From af19493cbf7cd54e43135a3bc3d94fcfe229ef3f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 19 Jan 2020 14:13:29 +0100 Subject: [PATCH 01/71] Add new resource SqlServerAudit --- CHANGELOG.md | 1 + .../DSC_SqlServerAudit.psm1 | 899 ++++++++++++++++++ .../DSC_SqlServerAudit.schema.mof | 20 + .../en-US/DSC_SqlServerAudit.strings.psd1 | 19 + .../SqlServerAudit/1-AddFileAudit.ps1 | 34 + .../SqlServerAudit/2-AddSecuritylogAudit.ps1 | 30 + ...3-AddSecuritylogAuditOnFailureShutdown.ps1 | 32 + .../4-AddSecuritylogAuditWithFilter.ps1 | 32 + .../SqlServerAudit/5-RemoveAudit.ps1 | 28 + .../DSC_SqlServerAudit.Integration.Tests.ps1 | 251 +++++ .../Integration/DSC_SqlServerAudit.config.ps1 | 145 +++ tests/Unit/DSC_SqlServerAudit.Tests.ps1 | 562 +++++++++++ 12 files changed, 2053 insertions(+) create mode 100644 source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 create mode 100644 source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof create mode 100644 source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 create mode 100644 source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 create mode 100644 source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 create mode 100644 source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 create mode 100644 source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 create mode 100644 source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 create mode 100644 tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_SqlServerAudit.config.ps1 create mode 100644 tests/Unit/DSC_SqlServerAudit.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d19bec022..e73bbc739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Set-SqlDscServerPermission` - Support for debugging of integration tests in AppVeyor. - Only run for pull requests + - Add new resource SqlServerAudit. - CommonTestHelper - `Import-SqlModuleStub` - Added the optional parameter **PasThru** that, if used, will return the diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 new file mode 100644 index 000000000..7c0c69f77 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 @@ -0,0 +1,899 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'SqlServerDsc.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'SqlServerDsc.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'DSC_SqlServerAudit' + + +<# + .SYNOPSIS + Returns the current state of the audit on a server. + + .PARAMETER Name + Specifies the name of the server audit to be added or removed. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exists. + + .PARAMETER InstanceName + Specifies the SQL instance in which the audit exists. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName + ) + + $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName + + Write-Verbose -Message ( + $script:localizedData.RetrievingAuditInfo -f $Name, $ServerName + ) + + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + ServerName = $ServerName + InstanceName = $InstanceName + DestinationType = $null + FilePath = $null + Filter = $null + MaximumFiles = $null + MaximumFileSize = $null + MaximumFileSizeUnit = $null + MaximumRolloverFiles = $null + OnFailure = $null + QueueDelay = $null + ReserveDiskSpace = $null + Enabled = $false + } + + # Check if database exists. + $sqlServerAuditObject = $sqlServerObject.Audits[$Name] + + if ($sqlServerAuditObject) + { + Write-Verbose -Message ( + $script:localizedData.AuditExist -f $Name, $ServerName + ) + + $returnValue['Ensure'] = 'Present' + $returnValue['DestinationType'] = $sqlServerAuditObject.DestinationType + $returnValue['FilePath'] = $sqlServerAuditObject.FilePath + $returnValue['MaximumFiles'] = $sqlServerAuditObject.MaximumFiles + $returnValue['MaximumFileSize'] = $sqlServerAuditObject.MaximumFileSize + $returnValue['MaximumFileSizeUnit'] = $sqlServerAuditObject.MaximumFileSizeUnit + $returnValue['MaximumRolloverFiles'] = $sqlServerAuditObject.MaximumRolloverFiles + $returnValue['ReserveDiskSpace'] = $sqlServerAuditObject.ReserveDiskSpace + $returnValue['Filter'] = $sqlServerAuditObject.Filter + $returnValue['OnFailure'] = $sqlServerAuditObject.OnFailure + $returnValue['QueueDelay'] = $sqlServerAuditObject.QueueDelay + $returnValue['Enabled'] = $sqlServerAuditObject.Enabled + } + + return $returnValue +} + +<# + .SYNOPSIS + sets the server audit in desired state. + + .PARAMETER Name + Specifies the name of the audit to be tested. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance where the audit should be tested. + + .PARAMETER DestinationType + Specifies the location where the audit should write to. + This can be File, SecurityLog or ApplicationLog. + + .PARAMETER FilePath + Specifies the location where te log files wil be placed. + + .PARAMETER MaximumFiles + Specifies the number of files on disk. + + .PARAMETER MaximumFileSize + Specifies the maximum file size in units by parameter MaximumFileSizeUnit. + + .PARAMETER MaximumFileSizeUnit + Specifies the unit that is used for the file size. this can be KB, MB or GB. + + .PARAMETER MaximumRolloverFiles + Specifies the amount of files on disk before SQL Server starts reusing + the files. + + .PARAMETER OnFailure + Specifies what should happen when writing events to the store fails. + This can be CONTINUE, FAIL_OPERATION or SHUTDOWN. + + .PARAMETER QueueDelay + Specifies the maximum delay before a event is writen to the store. + When set to low this could impact server performance. + When set to high events could be missing when a server crashes. + + .PARAMETER ReserveDiskSpace + Specifies if the needed file space should be reserved. only needed + when writing to a file log. + + .PARAMETER Enabled + Specifies if the audit should be enabled. Defaults to $false. + + .PARAMETER Ensure + Specifies if the server audit should be present or absent. If 'Present' + then the audit will be added to the server and, if needed, the audit + will be updated. If 'Absent' then the audit will be removed from + the server. Defaults to 'Present'. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter()] + [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] + [System.String] + $DestinationType = 'SecurityLog', + + [Parameter()] + [System.String] + $FilePath, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.UInt32] + $MaximumFiles, + + [Parameter()] + [System.UInt32] + $MaximumFileSize = 10, + + [Parameter()] + [ValidateSet('KB', 'MB', 'GB')] + [System.String] + $MaximumFileSizeUnit = 'MB', + + [Parameter()] + [System.UInt32] + $MaximumRolloverFiles, + + [Parameter()] + [ValidateSet('CONTINUE', 'FAIL_OPERATION', 'SHUTDOWN')] + [System.String] + $OnFailure = 'CONTINUE', + + [Parameter()] + [System.UInt32] + $QueueDelay = 1000, + + [Parameter()] + [System.Boolean] + $ReserveDiskSpace = $false, + + [Parameter()] + [System.Boolean] + $Enabled = $false, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Boolean] + $Force = $false + ) + + Write-Verbose -Message ( + $script:localizedData.SetAudit -f $Name, $ServerName, $InstanceName + ) + + #sanitize user input. + if (($MaximumFiles) -and ($MaximumRolloverFiles)) + { + $errorMessage = $script:localizedData.ImpossibleFileCombination + New-InvalidOperationException -Message $errorMessage + } + if ($FilePath) + { + $FilePath = $FilePath.Trimend('\') + '\' + + #Test if audit file location exists, and create if it does not. + if (-not (Test-Path -Path $FilePath)) + { + Write-Verbose -Message ( + $script:localizedData.CreateFolder -f $FilePath.Trimend('\') + ) + New-Item -ItemType directory -Path $FilePath.Trimend('\') + } + } + + #parameters for the TargetResource cmdlet. + $getTargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + } + + # Get-TargetResource will also help us to test if the audit exist. + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + # Default parameters for the cmdlet Invoke-Query used throughout. + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + $recreateAudit = $false + + if ($getTargetResourceResult.Ensure -eq $Ensure) + { + if ($Ensure -eq 'Present') + { + # Update Audit properties, if needed drop and recreate. + if ($DestinationType -eq $getTargetResourceResult.DestinationType) + { + switch ($DestinationType) + { + 'File' + { + $strReserveDiskSpace = 'OFF' + if ($ReserveDiskSpace) + { + $strReserveDiskSpace = 'ON' + } + + $strFiles = '' + if ($MaximumFiles) + { + $strFiles = 'MAX_FILES = {0},' -f $MaximumFiles + } + if ($MaximumRolloverFiles) + { + $strFiles = 'MAX_ROLLOVER_FILES = {0},' -f $MaximumRolloverFiles + } + + $target = 'FILE ( + FILEPATH = N''{0}'', + MAXSIZE = {1} {2}, + {3} + RESERVE_DISK_SPACE = {4} )' -f + $FilePath, + $MaximumFileSize, + $MaximumFileSizeUnit, + $strFiles, + $strReserveDiskSpace + } + + 'SecurityLog' + { + $target = 'SECURITY_LOG' + } + + 'ApplicationLog' + { + $target = 'APPLICATION_LOG' + } + } + + $withPart = 'QUEUE_DELAY = {0}, + ON_FAILURE = {1}' -f + $QueueDelay, + $OnFailure + + $ServerAuditParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + Action = 'ALTER' + Target = $target + WithPart = $withPart + } + + #if curent audit state is enabled, disable it before edit. + if ($getTargetResourceResult.Enabled -eq $true) + { + Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName + } + + try + { + Set-ServerAudit @ServerAuditParameters + } + catch + { + $errorMessage = $script:localizedData.FailedUpdateAudit -f $Name, $ServerName, $InstanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + #if audit state was disabled for edit, Re-enable it. + if ($getTargetResourceResult.Enabled -eq $true) + { + Enable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName + } + } + else + { + <# + Current server audit has a diferent storage type, the + server audit needs to be re-created. + #> + Write-Verbose -Message ( + $script:localizedData.ChangingAuditDestinationType -f + $Name, + $getTargetResourceResult.DestinationType, + $DestinationType, + $ServerName, + $InstanceName + ) + + $recreateAudit = $true + } + + } + } + + # Throw if not opt-in to re-create database user. + if ($recreateAudit -and -not $Force) + { + $errorMessage = $script:localizedData.ForceNotEnabled + New-InvalidOperationException -Message $errorMessage + } + + if (($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) + { + # Drop the server audit. + try + { + Write-Verbose -Message ( + $script:localizedData.DropAudit -f $Name, $serverName, $instanceName + ) + + #if curent audit state is enabled, disable it before removal. + if ($getTargetResourceResult.Enabled -eq $true) + { + Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName + } + + Invoke-Query @invokeQueryParameters -Query ( + 'DROP SERVER AUDIT [{0}];' -f $Name + ) + } + catch + { + $errorMessage = $script:localizedData.FailedDropAudit -f $Name, $ServerName, $InstanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + + <# + This evaluation is made to handle creation and re-creation of a server + audit to minimize the logic when the user has a different storage type, or + when there are restrictions on altering an existing audit. + #> + if (($Ensure -eq 'Present' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) + { + # Create the audit. + Write-Verbose -Message ( + $script:localizedData.CreateAudit -f $Name, $ServerName, $InstanceName + ) + + switch ($DestinationType) + { + 'File' + { + $strReserveDiskSpace = 'OFF' + if ($ReserveDiskSpace) + { + $strReserveDiskSpace = 'ON' + } + + $strFiles = '' + if ($MaximumFiles) + { + $strFiles = 'MAX_FILES = {0},' -f $MaximumFiles + } + if ($MaximumRolloverFiles) + { + $strFiles = 'MAX_ROLLOVER_FILES = {0},' -f $MaximumRolloverFiles + } + + $target = 'FILE ( + FILEPATH = N''{0}'', + MAXSIZE = {1} {2}, + {3} + RESERVE_DISK_SPACE = {4} )' -f + $FilePath, + $MaximumFileSize, + $MaximumFileSizeUnit, + $strFiles, + $strReserveDiskSpace + } + + 'SecurityLog' + { + $target = 'SECURITY_LOG' + } + + 'ApplicationLog' + { + $target = 'APPLICATION_LOG' + } + } + + $withPart = 'QUEUE_DELAY = {0}, + ON_FAILURE = {1}' -f + $QueueDelay, + $OnFailure + + $ServerAuditParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + Action = 'CREATE' + Target = $target + WithPart = $withPart + } + + try + { + Set-ServerAudit @ServerAuditParameters + } + catch + { + $errorMessage = $script:localizedData.FailedCreateAudit -f $Name, $ServerName, $InstanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + + if ($Ensure -eq 'Present' -and $getTargetResourceResult.Filter -ne $Filter) + { + try + { + Write-Verbose -Message ( + $script:localizedData.AddFilter -f $Filter, $Name, $serverName, $instanceName + ) + + #if curent audit state is enabled, disable it before setting filter. + if ($getTargetResourceResult.Enabled -eq $true) + { + Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName + } + + if ($null -ne $Filter -and $Filter -ne '') + { + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT [{0}] WHERE {1};' -f $Name, $Filter + ) + } + else + { + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT [{0}] REMOVE WHERE;' -f $Name + ) + } + } + catch + { + $errorMessage = $script:localizedData.FailedAddFilter -f $Filter, $Name, $ServerName, $InstanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + + if ($Ensure -eq 'Present' -and $getTargetResourceResult.Enabled -ne $Enabled) + { + if ($Enabled -eq $true) + { + Enable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName + } + else + { + Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName + } + } +} + +<# + .SYNOPSIS + Determines if the server audit is in desired state. + + .PARAMETER Name + Specifies the name of the audit to be tested. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance where the audit should be tested. + + .PARAMETER DestinationType + Specifies the location where the audit should write to. + This can be File, SecurityLog or ApplicationLog. + + .PARAMETER FilePath + Specifies the location where te log files wil be placed. + + .PARAMETER MaximumFiles + Specifies the number of files on disk. + + .PARAMETER MaximumFileSize + Specifies the maximum file size in units by parameter MaximumFileSizeUnit. + + .PARAMETER MaximumFileSizeUnit + Specifies the unit that is used for the file size. this can be KB, MB or GB. + + .PARAMETER MaximumRolloverFiles + Specifies the amount of files on disk before SQL Server starts reusing + the files. + + .PARAMETER OnFailure + Specifies what should happen when writing events to the store fails. + This can be CONTINUE, FAIL_OPERATION or SHUTDOWN. + + .PARAMETER QueueDelay + Specifies the maximum delay before a event is writen to the store. + When set to low this could impact server performance. + When set to high events could be missing when a server crashes. + + .PARAMETER ReserveDiskSpace + Specifies if the needed file space should be reserved. only needed + when writing to a file log. + + .PARAMETER Enabled + Specifies if the audit should be enabled. Defaults to $false. + + .PARAMETER Ensure + Specifies if the server audit should be present or absent. If 'Present' + then the audit will be added to the server and, if needed, the audit + will be updated. If 'Absent' then the audit will be removed from + the server. Defaults to 'Present'. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter()] + [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] + [System.String] + $DestinationType = 'SecurityLog', + + [Parameter()] + [System.String] + $FilePath, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.UInt32] + $MaximumFiles = 10, + + [Parameter()] + [System.UInt32] + $MaximumFileSize = 10, + + [Parameter()] + [ValidateSet('KB', 'MB', 'GB')] + [System.String] + $MaximumFileSizeUnit = 'MB', + + [Parameter()] + [System.UInt32] + $MaximumRolloverFiles = '10', + + [Parameter()] + [ValidateSet('CONTINUE', 'FAIL_OPERATION', 'SHUTDOWN')] + [System.String] + $OnFailure = 'CONTINUE', + + [Parameter()] + [System.UInt32] + $QueueDelay = 1000, + + [Parameter()] + [System.Boolean] + $ReserveDiskSpace = $false, + + [Parameter()] + [System.Boolean] + $Enabled = $false, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Boolean] + $Force = $false + ) + + Write-Verbose -Message ( + $script:localizedData.EvaluateAudit -f $Name, $ServerName, $InstanceName + ) + + #sanitize user input. + if ($FilePath) + { + $FilePath = $FilePath.Trimend('\') + '\' + $PSBoundParameters['FilePath'] = $FilePath.Trimend('\') + '\' + } + + $TargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + } + + # Get-TargetResource will also help us to test if the audit exist. + $getTargetResourceResult = Get-TargetResource @TargetResourceParameters + + if ($getTargetResourceResult.Ensure -eq $Ensure) + { + if ($Ensure -eq 'Present') + { + <# + Make sure default values are part of desired values if the user did + not specify them in the configuration. + #> + $desiredValues = @{ } + $PSBoundParameters + $desiredValues['Ensure'] = $Ensure + + $testTargetResourceReturnValue = Test-DscParameterState -CurrentValues $getTargetResourceResult ` + -DesiredValues $desiredValues ` + -ValuesToCheck @( + 'FilePath' + 'MaximumFileSize' + 'MaximumFileSizeUnit' + 'QueueDelay' + 'OnFailure' + 'Enabled' + 'Ensure' + 'DestinationType' + 'MaximumFiles' + 'MaximumRolloverFiles' + 'ReserveDiskSpace' + 'Filter' + ) + <# + WORKAROUND for possible bug? + Test-DscParameterState does not see if a parameter is removed as parameter + but still exists in the DSC resource. + + When in desired state do some aditional tests. + When not in desired state, aditional testing is not needed. + #> + if ($testTargetResourceReturnValue) + { + if ($getTargetResourceResult.Filter -ne $Filter) + { + $testTargetResourceReturnValue = $false + } + } + } + else + { + $testTargetResourceReturnValue = $true + } + } + else + { + $testTargetResourceReturnValue = $false + } + + if ($testTargetResourceReturnValue) + { + Write-Verbose -Message $script:localizedData.InDesiredState + } + else + { + Write-Verbose -Message $script:localizedData.NotInDesiredState + } + + return $testTargetResourceReturnValue +} + +<# + .SYNOPSIS + Disables a server audit. + + .PARAMETER Name + Specifies the name of the server audit to be disabled. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the audit exist. +#> +function Disable-Audit +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT [{0}] WITH (STATE = OFF);' -f $Name + ) +} + +<# + .SYNOPSIS + Enables a server audit. + + .PARAMETER Name + Specifies the name of the server audit to enabled. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the audit exist. +#> +function Enable-Audit +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT [{0}] WITH (STATE = ON);' -f $Name + ) +} + +<# + .SYNOPSIS + Creates an server audit. Alters if already exists. + + .PARAMETER Name + Specifies the name of the server audit to be added or removed. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the database exist. + + .PARAMETER Action + Specifies if the audit should be created or altered. + + .PARAMETER Target + Specifies if the target is the securityLog, the applicationLog or a File Log. + When a File log it also should contain al the file information. + + .PARAMETER WithPart + Specifies what should go in the WITH part of the audit query. +#> +function Set-ServerAudit +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [ValidateSet('CREATE', 'ALTER')] + [System.String] + $Action, + + [Parameter()] + [System.String] + $Target, + + [Parameter()] + [System.String] + $WithPart + ) + + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + Invoke-Query @invokeQueryParameters -Query ( + '{0} SERVER AUDIT [{1}] TO {2} + WITH ( + {3} + );' -f + $Action, + $Name, + $Target, + $WithPart + ) +} + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof new file mode 100644 index 000000000..8b3ec0301 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof @@ -0,0 +1,20 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlServerAudit")] +class DSC_SqlServerAudit : OMI_BaseResource +{ + [Key, Description("Specifies the host name of the SQL Server on which the instance exist.")] String ServerName; + [Key, Description("Specifies the SQL instance in which the Audit exist.")] String InstanceName; + [Key, Description("Specifies the name of the SQL audit to be added or removed.")] String Name; + [Write, Description("Specifies the storage type of the server audit."), ValueMap{"File", "SecurityLog", "ApplicationLog"}, Values{"File", "SecurityLog", "ApplicationLog"}] String DestinationType; + [Write, Description("Specifies the path where the audit files are stored when DestinationType is set to File.")] String FilePath; + [Write, Description("Specifies if the audit should be enabled. Defaults to $false")] Boolean Enabled; + [Write, Description("Specifies the filter that should be used on the audit.")] String Filter; + [Write, Description("Specifies the number of file a file audit can have.")] UInt32 MaximumFiles; + [Write, Description("Specifies the maximum file size of an audit file")] UInt32 MaximumFileSize; + [Write, Description("Specifies the FileSize unit of an audit file"), ValueMap{"KB", "MB", "GB"}, Values{"KB", "MB", "GB"}] String MaximumFileSizeUnit; + [Write, Description("Specifies the number of file a file audit can have before a rollover starts")] UInt32 MaximumRolloverFiles; + [Write, Description("Specifies the maximum delay before a event is written to disk. defaults to 1000 ( 1 sec ). When set to low, performance can degrade. When set to high, events can get lost with a server crash.")] UInt32 QueueDelay; + [Write, Description("Specifies if the disk space for the audit files should be reserved. Defaults to $false")] Boolean ReserveDiskSpace; + [Write, Description("Specifies the type of the database user. Valid"), ValueMap{"CONTINUE", "FAIL_OPERATION", "SHUTDOWN"}, Values{"CONTINUE", "FAIL_OPERATION", "SHUTDOWN"}] String OnFailure; + [Write, Description("Specifies if the audit should be present or absent. If 'Present' then the audit will be added to the server and, if needed, the audit will be updated. If 'Absent' then the audit will be removed from the server. Defaults to 'Present'."), ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure; + [Write, Description("Specifies if it is allowed to re-create the server audit when the DestinationType changes. Defaults to $false not allowing server audits to be re-created.")] Boolean Force; +}; diff --git a/source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 b/source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 new file mode 100644 index 000000000..26e5b4746 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 @@ -0,0 +1,19 @@ +ConvertFrom-StringData @' + RetrievingAuditInfo = Retrieving information about Audit '{0}' from the server '{1}'. (SSA0001) + EvaluateAudit = Determining if the audit '{0}' on server '{1}' instance '{2}' is in the desired state. (SSA0002) + AuditExist = The audit '{0}' exist in on server '{1}'. (SSA0003) + InDesiredState = The audit is in desired state. (SSA0004) + NotInDesiredState = The audit is not in desired state. (SSA0005) + CreateAudit = Creating the audit '{0}' on server '{1}' instance '{2}'. (SSA0006) + FailedCreateAudit = Failed creating audit '{0}' on server '{1}' instance '{2}'. (SSA0007) + DropAudit = Removing the audit '{0}' from server '{1}' instance '{2}'. (SSA0008) + FailedDropAudit = Failed removing the audit '{0}' from server '{1}' instance '{2}'. (SSA0009) + SetAudit = Setting the audit '{0}' on server '{1}' instance '{2}' to the desired state. (SSA0010) + FailedUpdateAudit = Failed updating audit '{0}' on server '{1}' instance '{2}'. (SSA0011) + ChangingAuditDestinationType = The audit '{0}' currently has destination type '{1}', but expected it to be '{2}'. Re-creating audit '{0}' on server {3} instance '{4}'. (SSA0012) + CreateFolder = Creating folder {0}. (SSA0013) + AddFilter = Setting filter '{0}' of audit {1} on server '{2}' instance '{3}' to the desired state. (SSA0014) + FailedAddFilter = Failed setting filter '{0}' of audit {1} on server '{2}' instance '{3}'. (SSA0015) + ImpossibleFileCombination = Both MaximumFiles and MaximumRolloverFiles have been defined. This is not a supported configuration. (SSA0016) + ForceNotEnabled = Unable to re-create the server audit. The server audit needs to be re-created but the configuration has not opt-in to re-create the audit. To opt-in set the parameter Force to $true. (SSA0017) +'@ diff --git a/source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 b/source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 new file mode 100644 index 000000000..f483b556d --- /dev/null +++ b/source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 @@ -0,0 +1,34 @@ +<# + .EXAMPLE + This example shows how to ensure that an audit destination + is absent on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit FileAudit_Server + { + Ensure = 'Present' + ServerName = 'SQL2019-01' + InstanceName = 'INST01' + Name = 'FileAudit' + DestinationType = 'File' + FilePath = 'C:\Temp\audit' + MaximumFileSize = 10 + MaximumFileSizeUnit = 'MB' + MaximumRolloverFiles = 11 + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 b/source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 new file mode 100644 index 000000000..7e9b77e5c --- /dev/null +++ b/source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 @@ -0,0 +1,30 @@ +<# + .EXAMPLE + This example shows how to ensure that the windows security event log + audit destination is present on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + DestinationType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 b/source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 new file mode 100644 index 000000000..642c539cd --- /dev/null +++ b/source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 @@ -0,0 +1,32 @@ +<# + .EXAMPLE + This example shows how to ensure that the windows security event log + audit destination is present on the instance sqltest.company.local\DSC. + The server should shutdown when logging is not possible. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + DestinationType = 'SecurityLog' + OnFailure = 'SHUTDOWN' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 b/source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 new file mode 100644 index 000000000..c239011f7 --- /dev/null +++ b/source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 @@ -0,0 +1,32 @@ +<# + .EXAMPLE + This example shows how to ensure that the windows security event log + audit destination is present on the instance sqltest.company.local\DSC. + and adds a filter so only users with a name lie administrator are audited +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + DestinationType = 'SecurityLog' + Enabled = $true + Filter = '([server_principal_name] like ''%ADMINISTRATOR'')' + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 b/source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 new file mode 100644 index 000000000..d237c88dd --- /dev/null +++ b/source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 @@ -0,0 +1,28 @@ +<# + .EXAMPLE + This example shows how to ensure that an audit destination + is absent on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit FileAudit_Server + { + Ensure = 'Absent' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'FileAudit' + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 new file mode 100644 index 000000000..3bae5f9bd --- /dev/null +++ b/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 @@ -0,0 +1,251 @@ +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (-not (Test-BuildCategory -Type 'Integration' -Category @('Integration_SQL2016','Integration_SQL2017'))) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceFriendlyName = 'SqlServerAudit' +$script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + +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 'Integration' + +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_AddFileAudit_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 + $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 + $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 + $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 + $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_AddSecLogAudit_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_RemoveAudit1_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -BeNullOrEmpty + $resourceCurrentState.FilePath | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty + $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/tests/Integration/DSC_SqlServerAudit.config.ps1 b/tests/Integration/DSC_SqlServerAudit.config.ps1 new file mode 100644 index 000000000..d4c6a848a --- /dev/null +++ b/tests/Integration/DSC_SqlServerAudit.config.ps1 @@ -0,0 +1,145 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + + UserName = "$env:COMPUTERNAME\SqlAdmin" + Password = 'P@ssw0rd1' + + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQLTEST' + + AuditName1 = 'FileAudit' + DestinationType1 = 'File' + FilePath1 = 'C:\Temp\audit\' + MaximumFileSize1 = 10 + MaximumFileSizeUnit1 = 'MB' + MaximumRolloverFiles1 = 11 + + AuditName2 = 'SecLogAudit' + DestinationType2 = 'SecurityLog' + Filter2 = '([server_principal_name] like ''%ADMINISTRATOR'')' + } + ) + } +} + +<# + .SYNOPSIS + Creates a Server Audit with File destination. +#> +Configuration MSFT_SqlServerAudit_AddFileAudit_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName1 + DestinationType = $Node.DestinationType1 + FilePath = $Node.FilePath1 + MaximumFileSize = $Node.MaximumFileSize1 + MaximumFileSizeUnit = $Node.MaximumFileSizeUnit1 + MaximumRolloverFiles = $Node.MaximumRolloverFiles1 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Creates a audit to the securitylog, with a filer. +#> +Configuration MSFT_SqlServerAudit_AddSecLogAudit_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName2 + DestinationType = $Node.DestinationType2 + Filter = $Node.Filter2 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Should remove the filter +#> +Configuration MSFT_SqlServerAudit_AddSecLogAuditNoFilter_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName2 + DestinationType = $Node.DestinationType2 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Removes the file audit. +#> +Configuration MSFT_SqlServerAudit_RemoveAudit1_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_Test' + { + Ensure = 'Absent' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName1 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} diff --git a/tests/Unit/DSC_SqlServerAudit.Tests.ps1 b/tests/Unit/DSC_SqlServerAudit.Tests.ps1 new file mode 100644 index 000000000..928012a53 --- /dev/null +++ b/tests/Unit/DSC_SqlServerAudit.Tests.ps1 @@ -0,0 +1,562 @@ +<# + .SYNOPSIS + Automated unit test for DSC_SqlServerAudit DSC resource. + + .NOTES + To run this script locally, please make sure to first run the bootstrap + script. Read more at + https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment +#> + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (-not (Test-BuildCategory -Type 'Unit')) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceName = 'DSC_SqlServerAudit' + +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' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + + # Load the default SQL Module stub + Import-SQLModuleStub +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Invoke-TestSetup + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:dscResourceName { + $mockServerName = 'SERVER01' + $mockInstanceName = 'INSTANCE' + $mockAuditName = 'FileAudit' + + # TODO: Update type and get the correct values for this. + $mockAuditObject = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAuditName -PassThru | + Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | + Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force + + $mockConnectSql = { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'Audits' -Value { + return @( + @{ + $mockAuditName = $mockAuditObject + } + ) + } -PassThru -Force + } + + $defaultParameters = @{ + InstanceName = $mockInstanceName + ServerName = $mockServerName + Name = $mockAuditName + Verbose = $true + } + + Describe 'DSC_SqlServerAudit\Get-TargetResource' -Tag 'Get' { + BeforeAll { + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + } + + Context 'When the system is not in the desired state' { + BeforeEach { + $testParameters = $defaultParameters.Clone() + $testParameters.Name = 'UnknownAudit' + } + + It 'Should call the mock function Connect-SQL' { + { Get-TargetResource @testParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the desired state as absent' { + $result = Get-TargetResource @testParameters + + $result.Ensure | Should -Be 'Absent' + $result.Enabled | Should -BeFalse + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + + $result.Name | Should -Be $testParameters.Name + $result.ServerName | Should -Be $testParameters.ServerName + $result.InstanceName | Should -Be $testParameters.InstanceName + } + + It 'Should return $null for the rest of the properties' { + $result = Get-TargetResource @testParameters + + $result.DestinationType | Should -BeNullOrEmpty + $result.FilePath | Should -BeNullOrEmpty + $result.Filter | Should -BeNullOrEmpty + $result.MaximumFiles | Should -BeNullOrEmpty + $result.MaximumFileSize | Should -BeNullOrEmpty + $result.MaximumFileSizeUnit | Should -BeNullOrEmpty + $result.MaximumRolloverFiles | Should -BeNullOrEmpty + $result.OnFailure | Should -BeNullOrEmpty + $result.QueueDelay | Should BeNullOrEmpty + $result.ReserveDiskSpace | Should -BeNullOrEmpty + } + } + + Context 'When the system is in the desired state' { + BeforeEach { + $testParameters = $defaultParameters.Clone() + } + + It 'Should call the mock function Connect-SQL' { + { Get-TargetResource @testParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the desired state as present' { + $result = Get-TargetResource @testParameters + + $result.Ensure | Should -Be 'Present' + $result.Enabled | Should -BeTrue + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + + $result.Name | Should -Be $testParameters.Name + $result.ServerName | Should -Be $testParameters.ServerName + $result.InstanceName | Should -Be $testParameters.InstanceName + } + + It 'Should return the correct value for the rest of the properties' { + $result = Get-TargetResource @testParameters + + $result.DestinationType | Should -Be 'File' + $result.FilePath | Should -BeNullOrEmpty + $result.Filter | Should -BeNullOrEmpty + $result.MaximumFiles | Should -BeNullOrEmpty + $result.MaximumFileSize | Should -BeNullOrEmpty + $result.MaximumFileSizeUnit | Should -BeNullOrEmpty + $result.MaximumRolloverFiles | Should -BeNullOrEmpty + $result.OnFailure | Should -BeNullOrEmpty + $result.QueueDelay | Should BeNullOrEmpty + $result.ReserveDiskSpace | Should -BeNullOrEmpty + } + } + } + + # Describe 'DSC_SqlServerAudit\Test-TargetResource' -Tag 'Test' { + # BeforeEach { + # $testParameters = $defaultParameters.Clone() + + # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + # } + + # Context 'When the system is not in the desired state' { + # # Make sure the mock does not return the correct endpoint + # $mockDynamicEndpointName = $mockOtherEndpointName + + # It 'Should return that desired state is absent when wanted desired state is to be Present (using default values)' { + # $testParameters.Add('Ensure', 'Present') + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # It 'Should return that desired state is absent when wanted desired state is to be Present (setting all parameters)' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Make sure the mock do return the correct endpoint + # $mockDynamicEndpointName = $mockEndpointName + + # It 'Should return that desired state is absent when wanted desired state is to be Absent' { + # $testParameters.Add('Ensure', 'Absent') + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener port + # $mockDynamicEndpointName = $mockEndpointName + # $mockDynamicEndpointListenerPort = $mockOtherEndpointListenerPort + + # Context 'When listener port is not in desired state' { + # It 'Should return that desired state is absent' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } + + # # Make sure the mock do return the correct endpoint listener port + # $mockDynamicEndpointListenerPort = $mockEndpointListenerPort + + # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener IP address + # $mockDynamicEndpointName = $mockEndpointName + # $mockDynamicEndpointListenerIpAddress = $mockOtherEndpointListenerIpAddress + + # Context 'When listener IP address is not in desired state' { + # It 'Should return that desired state is absent' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } + + # # Make sure the mock do return the correct endpoint listener IP address + # $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress + + # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint owner + # $mockDynamicEndpointName = $mockEndpointName + # $mockDynamicEndpointOwner = $mockOtherEndpointOwner + + # Context 'When listener Owner is not in desired state' { + # It 'Should return that desired state is absent' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Owner', $mockEndpointOwner) + + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } + + # # Make sure the mock do return the correct endpoint owner + # $mockDynamicEndpointOwner = $mockEndpointOwner + # } + + # Context 'When the system is in the desired state' { + # # Make sure the mock do return the correct endpoint + # $mockDynamicEndpointName = $mockEndpointName + + # It 'Should return that desired state is present when wanted desired state is to be Present (using default values)' { + # $result = Test-TargetResource @testParameters + # $result | Should -Be $true + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Make sure the mock does not return the correct endpoint + # $mockDynamicEndpointName = $mockOtherEndpointName + + # It 'Should return that desired state is present when wanted desired state is to be Absent' { + # $testParameters.Add('Ensure', 'Absent') + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $true + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } + + # Assert-VerifiableMock + # } + + # Describe 'DSC_SqlServerAudit\Set-TargetResource' -Tag 'Set' { + # BeforeEach { + # $testParameters = $defaultParameters.Clone() + + # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + # Mock -CommandName New-Object -MockWith $mockNewObjectEndPoint -ParameterFilter $mockNewObjectEndPoint_ParameterFilter -Verifiable + # } + + # Context 'When the system is not in the desired state' { + # # Make sure the mock do return the correct endpoint + # $mockDynamicEndpointName = $mockEndpointName + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Create() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call the method Create when desired state is to be Present (using default values)' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Absent' + # } + # } -Verifiable + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $true + # $script:mockMethodStartRan | Should -Be $true + # $script:mockMethodAlterRan | Should -Be $false + # $script:mockMethodDropRan | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Create() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call the method Create when desired state is to be Present (setting all parameters)' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Absent' + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $true + # $script:mockMethodStartRan | Should -Be $true + # $script:mockMethodAlterRan | Should -Be $false + # $script:mockMethodDropRan | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Drop() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call the method Drop when desired state is to be Absent' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Absent') + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $false + # $script:mockMethodDropRan | Should -Be $true + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Alter() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call Alter method when listener port is not in desired state' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockOtherEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $true + # $script:mockMethodDropRan | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Alter() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call Alter method when listener IP address is not in desired state' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockOtherEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $true + # $script:mockMethodDropRan | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Alter() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call Alter method when Owner is not in desired state' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockOtherEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $true + # $script:mockMethodDropRan | Should -Be $false + + # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Make sure the mock does not return the correct endpoint + # $mockDynamicEndpointName = $mockOtherEndpointName + + # Context 'When endpoint is missing when Ensure is set to Present' { + # It 'Should throw the correct error' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # Owner = $mockEndpointOwner + # } + # } -Verifiable + + # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) + # } + # } + + # Context 'When endpoint is missing when Ensure is set to Absent' { + # It 'Should throw the correct error' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # Owner = $mockEndpointOwner + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Absent') + + # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) + # } + # } + + # Context 'When Connect-SQL returns nothing' { + # It 'Should throw the correct error' { + # Mock -CommandName Get-TargetResource -Verifiable + # Mock -CommandName Connect-SQL -MockWith { + # return $null + # } + + # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.NotConnectedToInstance -f $testParameters.ServerName, $testParameters.InstanceName) + # } + # } + # } + + + # Assert-VerifiableMock + # } + } +} +finally +{ + Invoke-TestCleanup +} From 9adf983ab72e7346789ac29c5f5e4c1c88c2b409 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 19 Jan 2020 14:13:43 +0100 Subject: [PATCH 02/71] Add new resource SqlServerAuditSpecification --- CHANGELOG.md | 1 + .../DSC_SqlServerAuditSpecification.psm1 | 1426 +++++++++++++++++ ...DSC_SqlServerAuditSpecification.schema.mof | 53 + ...C_SqlServerAuditSpecification.strings.psd1 | 17 + source/Examples/README.md | 2 + ...-AddServerAuditSpecificationAdminAudit.ps1 | 65 + ...-AddServerAuditSpecificationLoginAudit.ps1 | 49 + .../3-AddServerAuditAuditChange.ps1 | 44 + .../4-AddMultipleServerAudits.ps1 | 92 ++ .../5-RemoveAuditSpecification.ps1 | 29 + ...erAuditSpecification.Integration.Tests.ps1 | 252 +++ ...DSC_SqlServerAuditSpecification.config.ps1 | 283 ++++ 12 files changed, 2313 insertions(+) create mode 100644 source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 create mode 100644 source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof create mode 100644 source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 create mode 100644 source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 create mode 100644 source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 create mode 100644 source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 create mode 100644 source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 create mode 100644 source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 create mode 100644 tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e73bbc739..a8101f2fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - Add new resource SqlServerAudit. + - Add new resource SqlServerAuditSpecification. - CommonTestHelper - `Import-SqlModuleStub` - Added the optional parameter **PasThru** that, if used, will return the diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 new file mode 100644 index 000000000..19dd40a0f --- /dev/null +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 @@ -0,0 +1,1426 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'SqlServerDsc.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'SqlServerDsc.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'DSC_SqlServerAuditSpecification' + +<# + .SYNOPSIS + Returns the current state of the server audit specification. + + .PARAMETER Name + Specifies the name of the server audit specification to be added or removed. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the server audit specification exist. + + .NOTES + DO NOT CHANGE THE CAPITALS IN THE PARAMETERS! These are used to find the + places to insert an underscore, see function Get-DatabaseObjectNameFromPSParamName + for more information. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName + ) + + Write-Verbose -Message ( + $script:localizedData.RetrievingAuditSpecificationInformation -f + $Name, + $ServerName, + $InstanceName + ) + + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + ServerName = $ServerName + InstanceName = $InstanceName + AuditName = '' + ApplicationRoleChangePasswordGroup = $false + AuditChangeGroup = $false + BackupRestoreGroup = $false + BrokerLoginGroup = $false + DatabaseChangeGroup = $false + DatabaseLogoutGroup = $false + DatabaseMirroringLoginGroup = $false + DatabaseObjectAccessGroup = $false + DatabaseObjectChangeGroup = $false + DatabaseObjectOwnershipChangeGroup = $false + DatabaseObjectPermissionChangeGroup = $false + DatabaseOperationGroup = $false + DatabaseOwnershipChangeGroup = $false + DatabasePermissionChangeGroup = $false + DatabasePrincipalChangeGroup = $false + DatabasePrincipalImpersonationGroup = $false + DatabaseRoleMemberChangeGroup = $false + DbccGroup = $false + FailedDatabaseAuthenticationGroup = $false + FailedLoginGroup = $false + FulltextGroup = $false + LoginChangePasswordGroup = $false + LogoutGroup = $false + SchemaObjectAccessGroup = $false + SchemaObjectChangeGroup = $false + SchemaObjectOwnershipChangeGroup = $false + SchemaObjectPermissionChangeGroup = $false + ServerObjectChangeGroup = $false + ServerObjectOwnershipChangeGroup = $false + ServerObjectPermissionChangeGroup = $false + ServerOperationGroup = $false + ServerPermissionChangeGroup = $false + ServerPrincipalChangeGroup = $false + ServerPrincipalImpersonationGroup = $false + ServerRoleMemberChangeGroup = $false + ServerStateChangeGroup = $false + SuccessfulDatabaseAuthenticationGroup = $false + SuccessfulLoginGroup = $false + TraceChangeGroup = $false + UserChangePasswordGroup = $false + UserDefinedAuditGroup = $false + TransactionGroup = $false + Enabled = $false + } + + $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName + + # Default parameters for the cmdlet Invoke-Query used throughout. + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + $DataSetAudit = Invoke-Query @invokeQueryParameters -WithResults -Query ( + 'Select + s.server_specification_id, + s.is_state_enabled, + a.name as auditName + FROM sys.server_audits AS a + JOIN sys.server_audit_specifications AS s + ON a.audit_guid = s.audit_guid + where s.name = ''{0}''' -f + $Name) + + if ($null -ne $DataSetAudit -and $DataSetAudit.Tables[0].Rows.Count -gt 0) + { + Write-Verbose -Message ( + $script:localizedData.AuditSpecificationExist -f $Name, $ServerName, $InstanceName + ) + + $dataSetRow = $DataSetAudit.Tables[0].Rows[0] + + $DataSetAuditSpecification = Invoke-Query @invokeQueryParameters -WithResults -Query ( + 'Select audit_action_name + from sys.server_audit_specification_details + where server_specification_id = {0}' -f + $dataSetRow.server_specification_id) + + #this should always happen!!!!! + if ($null -ne $DataSetAuditSpecification -and $DataSetAuditSpecification.Tables.Count -gt 0) + { + $resultSet = Convert-ToHashTable -DataTable $DataSetAuditSpecification.Tables[0] + + $returnValue['Ensure'] = 'Present' + $returnValue['AuditName'] = $dataSetRow.auditName + $returnValue['ApplicationRoleChangePasswordGroup'] = $resultSet['APPLICATION_ROLE_CHANGE_PASSWORD_GROUP'] + $returnValue['AuditChangeGroup'] = $resultSet['AUDIT_CHANGE_GROUP'] + $returnValue['BackupRestoreGroup'] = $resultSet['BACKUP_RESTORE_GROUP'] + $returnValue['BrokerLoginGroup'] = $resultSet['BROKER_LOGIN_GROUP'] + $returnValue['DatabaseChangeGroup'] = $resultSet['DATABASE_CHANGE_GROUP'] + $returnValue['DatabaseLogoutGroup'] = $resultSet['DATABASE_LOGOUT_GROUP'] + $returnValue['DatabaseMirroringLoginGroup'] = $resultSet['DATABASE_MIRRORING_LOGIN_GROUP'] + $returnValue['DatabaseObjectAccessGroup'] = $resultSet['DATABASE_OBJECT_ACCESS_GROUP'] + $returnValue['DatabaseObjectChangeGroup'] = $resultSet['DATABASE_OBJECT_CHANGE_GROUP'] + $returnValue['DatabaseObjectOwnershipChangeGroup'] = $resultSet['DATABASE_OBJECT_OWNERSHIP_CHANGE_GROUP'] + $returnValue['DatabaseObjectPermissionChangeGroup'] = $resultSet['DATABASE_OBJECT_PERMISSION_CHANGE_GROUP'] + $returnValue['DatabaseOperationGroup'] = $resultSet['DATABASE_OPERATION_GROUP'] + $returnValue['DatabaseOwnershipChangeGroup'] = $resultSet['DATABASE_OWNERSHIP_CHANGE_GROUP'] + $returnValue['DatabasePermissionChangeGroup'] = $resultSet['DATABASE_PERMISSION_CHANGE_GROUP'] + $returnValue['DatabasePrincipalChangeGroup'] = $resultSet['DATABASE_PRINCIPAL_CHANGE_GROUP'] + $returnValue['DatabasePrincipalImpersonationGroup'] = $resultSet['DATABASE_PRINCIPAL_IMPERSONATION_GROUP'] + $returnValue['DatabaseRoleMemberChangeGroup'] = $resultSet['DATABASE_ROLE_MEMBER_CHANGE_GROUP'] + $returnValue['DbccGroup'] = $resultSet['DBCC_GROUP'] + $returnValue['FailedDatabaseAuthenticationGroup'] = $resultSet['FAILED_DATABASE_AUTHENTICATION_GROUP'] + $returnValue['FailedLoginGroup'] = $resultSet['FAILED_LOGIN_GROUP'] + $returnValue['FulltextGroup'] = $resultSet['FULLTEXT_GROUP'] + $returnValue['LoginChangePasswordGroup'] = $resultSet['LOGIN_CHANGE_PASSWORD_GROUP'] + $returnValue['LogoutGroup'] = $resultSet['LOGOUT_GROUP'] + $returnValue['SchemaObjectAccessGroup'] = $resultSet['SCHEMA_OBJECT_ACCESS_GROUP'] + $returnValue['SchemaObjectChangeGroup'] = $resultSet['SCHEMA_OBJECT_CHANGE_GROUP'] + $returnValue['SchemaObjectOwnershipChangeGroup'] = $resultSet['SCHEMA_OBJECT_OWNERSHIP_CHANGE_GROUP'] + $returnValue['SchemaObjectPermissionChangeGroup'] = $resultSet['SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP'] + $returnValue['ServerObjectChangeGroup'] = $resultSet['SERVER_OBJECT_CHANGE_GROUP'] + $returnValue['ServerObjectOwnershipChangeGroup'] = $resultSet['SERVER_OBJECT_OWNERSHIP_CHANGE_GROUP'] + $returnValue['ServerObjectPermissionChangeGroup'] = $resultSet['SERVER_OBJECT_PERMISSION_CHANGE_GROUP'] + $returnValue['ServerOperationGroup'] = $resultSet['SERVER_OPERATION_GROUP'] + $returnValue['ServerPermissionChangeGroup'] = $resultSet['SERVER_PERMISSION_CHANGE_GROUP'] + $returnValue['ServerPrincipalChangeGroup'] = $resultSet['SERVER_PRINCIPAL_CHANGE_GROUP'] + $returnValue['ServerPrincipalImpersonationGroup'] = $resultSet['SERVER_PRINCIPAL_IMPERSONATION_GROUP'] + $returnValue['ServerRoleMemberChangeGroup'] = $resultSet['SERVER_ROLE_MEMBER_CHANGE_GROUP'] + $returnValue['ServerStateChangeGroup'] = $resultSet['SERVER_STATE_CHANGE_GROUP'] + $returnValue['SuccessfulDatabaseAuthenticationGroup'] = $resultSet['SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP'] + $returnValue['SuccessfulLoginGroup'] = $resultSet['SUCCESSFUL_LOGIN_GROUP'] + $returnValue['TraceChangeGroup'] = $resultSet['TRACE_CHANGE_GROUP'] + $returnValue['UserChangePasswordGroup'] = $resultSet['USER_CHANGE_PASSWORD_GROUP'] + $returnValue['UserDefinedAuditGroup'] = $resultSet['USER_DEFINED_AUDIT_GROUP'] + $returnValue['TransactionGroup'] = $resultSet['TRANSACTION_GROUP'] + $returnValue['Enabled'] = $dataSetRow.is_state_enabled + } + } + return $returnValue +} + +<# + .SYNOPSIS + Creates, removes or updates a server audit specification to it's desired state. + + .PARAMETER Name + Specifies the name of the server audit specification to be added or removed. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the server audit specification exist. + + .PARAMETER Ensure + Specifies if the server audit specification should be present or absent. + If 'Present' then the server audit specification will be added to the instance + and, if needed, the server audit specification will be updated. If 'Absent' + then the server audit specification will be removed from the instance. + Defaults to 'Present'. + + .PARAMETER AuditName + Specifies the audit that should be used to store the server audit specification + events. + + .PARAMETER Enabled + Specifies if the server audit specification should be enabled or disabled. + + .PARAMETER ApplicationRoleChangePasswordGroup + Specifies if this audit specification should be on or off + + .PARAMETER AuditChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER BackupRestoreGroup + Specifies if this audit specification should be on or off + + .PARAMETER BrokerLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseLogoutGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseMirroringLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectAccessGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseOperationGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabasePermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabasePrincipalChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabasePrincipalImpersonationGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseRoleMemberChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DbccGroup + Specifies if this audit specification should be on or off + + .PARAMETER FailedDatabaseAuthenticationGroup + Specifies if this audit specification should be on or off + + .PARAMETER FailedLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER FulltextGroup + Specifies if this audit specification should be on or off + + .PARAMETER LoginChangePasswordGroup + Specifies if this audit specification should be on or off + + .PARAMETER LogoutGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectAccessGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerObjectChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerObjectOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerObjectPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerOperationGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerPrincipalChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerPrincipalImpersonationGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerRoleMemberChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerStateChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER SuccessfulDatabaseAuthenticationGroup + Specifies if this audit specification should be on or off + + .PARAMETER SuccessfulLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER TraceChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER UserChangePasswordGroup + Specifies if this audit specification should be on or off + + .PARAMETER UserDefinedAuditGroup + Specifies if this audit specification should be on or off + + .PARAMETER TransactionGroup + Specifies if this audit specification should be on or off +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter()] + [System.String] + $AuditName, + + [Parameter()] + [System.Boolean] + $ApplicationRoleChangePasswordGroup = $false, + + [Parameter()] + [System.Boolean] + $AuditChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $BackupRestoreGroup = $false, + + [Parameter()] + [System.Boolean] + $BrokerLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseLogoutGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseMirroringLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectAccessGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseOperationGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabasePermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabasePrincipalChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabasePrincipalImpersonationGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseRoleMemberChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DbccGroup = $false, + + [Parameter()] + [System.Boolean] + $FailedDatabaseAuthenticationGroup = $false, + + [Parameter()] + [System.Boolean] + $FailedLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $FulltextGroup = $false, + + [Parameter()] + [System.Boolean] + $LoginChangePasswordGroup = $false, + + [Parameter()] + [System.Boolean] + $LogoutGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectAccessGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerObjectChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerObjectOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerObjectPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerOperationGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerPrincipalChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerPrincipalImpersonationGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerRoleMemberChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerStateChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $SuccessfulDatabaseAuthenticationGroup = $false, + + [Parameter()] + [System.Boolean] + $SuccessfulLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $TraceChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $UserChangePasswordGroup = $false, + + [Parameter()] + [System.Boolean] + $UserDefinedAuditGroup = $false, + + [Parameter()] + [System.Boolean] + $TransactionGroup = $false, + + [Parameter()] + [System.Boolean] + $Enabled = $false, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Boolean] + $Force = $false + ) + + Write-Verbose -Message ( + $script:localizedData.SetAuditSpecification -f $Name, $ServerName, $InstanceName + ) + + $TargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + } + + # Get-TargetResource will also help us to test if the database exist. + $getTargetResourceResult = Get-TargetResource @TargetResourceParameters + + # Default parameters for the cmdlet Invoke-Query used throughout. + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + $desiredValues = @{ } + $PSBoundParameters + + $auditSpecificationAddDropString = Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues + Write-Verbose -Message $( + Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues + ) + + $recreateAudit = $false + + if ($getTargetResourceResult.Ensure -eq $Ensure) + { + if ($Ensure -eq 'Present') + { + # Update, if needed. + Write-Verbose -Message ( + $script:localizedData.CreateAuditSpecification -f $Name, $serverName, $instanceName + ) + + Disable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName + + try + { + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT SPECIFICATION [{0}] FOR SERVER AUDIT [{1}] {2}' -f + $Name, + $AuditName, + $auditSpecificationAddDropString + ) + } + catch + { + #If something went wrong, try to recreate te resource. + $recreateAudit = $true + + $errorMessage = $script:localizedData.FailedUpdateAuditSpecification -f $Name, $serverName, $instanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + + if ($Enabled -eq $true) + { + Enable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName + } + } + } + + # Throw if not opt-in to re-create server audit specification. + if ($recreateAudit -and -not $Force) + { + $errorMessage = $script:localizedData.ForceNotEnabled + New-InvalidOperationException -Message $errorMessage + } + + if (($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) + { + Write-Verbose -Message ( + $script:localizedData.DropAuditSpecification -f $Name, $serverName, $instanceName + ) + + Disable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName + + # Drop the server audit. + try + { + Invoke-Query @invokeQueryParameters -Query ( + 'DROP SERVER AUDIT SPECIFICATION [{0}];' -f $Name + ) + } + catch + { + $errorMessage = $script:localizedData.FailedDropAuditSpecification -f $Name, $serverName, $instanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + + <# + This evaluation is made to handle creation and re-creation of a database + user to minimize the logic when the user has a different user type, or + when there are restrictions on altering an existing database user. + #> + if (($Ensure -eq 'Present' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) + { + Write-Verbose -Message ( + $script:localizedData.CreateAuditSpecification -f $Name, $serverName, $instanceName + ) + + try + { + #when target audit already has an audit specification, graceful abort. + #only one audit spec for each audit per database/server can exist. + $DataSetAudit = Invoke-Query @invokeQueryParameters -WithResults -Query ( + 'select sas.name from + sys.server_audits sa inner join + sys.server_audit_specifications sas + on sa.audit_guid = sas.audit_guid + where sa.name = ''{0}''' -f + $AuditName) + if ($null -eq $DataSetAudit -or $DataSetAudit.Tables[0].Rows.Count -gt 0) + { + $errorMessage = $script:localizedData.AuditAlreadyInUse -f + $AuditName, + $Name, + $serverName, + $instanceName, + $DataSetAudit.Tables[0].Rows[0] + New-InvalidOperationException -Message $errorMessage #-ErrorRecord $_ + } + } + catch + { + $errorMessage = $script:localizedData.FailedCreateAuditSpecification -f $Name, $serverName, $instanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + + # Create, if needed and posible. + try + { + Invoke-Query @invokeQueryParameters -Query ( + 'CREATE SERVER AUDIT SPECIFICATION [{0}] FOR SERVER AUDIT [{1}] {2}' -f + $Name, + $AuditName, + $auditSpecificationAddDropString) + + if ($Enabled -eq $true) + { + Enable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName + } + } + catch + { + $errorMessage = $script:localizedData.FailedCreateAuditSpecification -f $Name, $serverName, $instanceName + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } +} + + + +<# + .SYNOPSIS + Determines if the server audit specification is in desired state. + + .PARAMETER Name + Specifies the name of the server audit specification to be added or removed. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the server audit specification exist. + + .PARAMETER Ensure + Specifies if the server audit specification should be present or absent. + If 'Present' then the server audit specification will be added to the instance + and, if needed, the server audit specification will be updated. If 'Absent' + then the server audit specification will be removed from the instance. + Defaults to 'Present'. + + .PARAMETER AuditName + Specifies the audit that should be used to store the server audit specification + events. + + .PARAMETER Enabled + Specifies if the server audit specification should be enabled or disabled. + + .PARAMETER ApplicationRoleChangePasswordGroup + Specifies if this audit specification should be on or off + + .PARAMETER AuditChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER BackupRestoreGroup + Specifies if this audit specification should be on or off + + .PARAMETER BrokerLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseLogoutGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseMirroringLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectAccessGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseObjectPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseOperationGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabasePermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabasePrincipalChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabasePrincipalImpersonationGroup + Specifies if this audit specification should be on or off + + .PARAMETER DatabaseRoleMemberChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER DbccGroup + Specifies if this audit specification should be on or off + + .PARAMETER FailedDatabaseAuthenticationGroup + Specifies if this audit specification should be on or off + + .PARAMETER FailedLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER FulltextGroup + Specifies if this audit specification should be on or off + + .PARAMETER LoginChangePasswordGroup + Specifies if this audit specification should be on or off + + .PARAMETER LogoutGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectAccessGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER SchemaObjectPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerObjectChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerObjectOwnershipChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerObjectPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerOperationGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerPermissionChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerPrincipalChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerPrincipalImpersonationGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerRoleMemberChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER ServerStateChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER SuccessfulDatabaseAuthenticationGroup + Specifies if this audit specification should be on or off + + .PARAMETER SuccessfulLoginGroup + Specifies if this audit specification should be on or off + + .PARAMETER TraceChangeGroup + Specifies if this audit specification should be on or off + + .PARAMETER UserChangePasswordGroup + Specifies if this audit specification should be on or off + + .PARAMETER UserDefinedAuditGroup + Specifies if this audit specification should be on or off + + .PARAMETER TransactionGroup + Specifies if this audit specification should be on or off +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter()] + [System.String] + $AuditName, + + [Parameter()] + [System.Boolean] + $ApplicationRoleChangePasswordGroup = $false, + + [Parameter()] + [System.Boolean] + $AuditChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $BackupRestoreGroup = $false, + + [Parameter()] + [System.Boolean] + $BrokerLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseLogoutGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseMirroringLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectAccessGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseObjectPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseOperationGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabasePermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabasePrincipalChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabasePrincipalImpersonationGroup = $false, + + [Parameter()] + [System.Boolean] + $DatabaseRoleMemberChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $DbccGroup = $false, + + [Parameter()] + [System.Boolean] + $FailedDatabaseAuthenticationGroup = $false, + + [Parameter()] + [System.Boolean] + $FailedLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $FulltextGroup = $false, + + [Parameter()] + [System.Boolean] + $LoginChangePasswordGroup = $false, + + [Parameter()] + [System.Boolean] + $LogoutGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectAccessGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $SchemaObjectPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerObjectChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerObjectOwnershipChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerObjectPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerOperationGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerPermissionChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerPrincipalChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerPrincipalImpersonationGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerRoleMemberChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $ServerStateChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $SuccessfulDatabaseAuthenticationGroup = $false, + + [Parameter()] + [System.Boolean] + $SuccessfulLoginGroup = $false, + + [Parameter()] + [System.Boolean] + $TraceChangeGroup = $false, + + [Parameter()] + [System.Boolean] + $UserChangePasswordGroup = $false, + + [Parameter()] + [System.Boolean] + $UserDefinedAuditGroup = $false, + + [Parameter()] + [System.Boolean] + $TransactionGroup = $false, + + [Parameter()] + [System.Boolean] + $Enabled = $false, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Boolean] + $Force = $false + ) + + Write-Verbose -Message ( + $script:localizedData.EvaluateAuditSpecification -f $Name, $ServerName, $InstanceName + ) + + $TargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + } + + # Get-TargetResource will also help us to test if the audit exist. + $getTargetResourceResult = Get-TargetResource @TargetResourceParameters + + if ($getTargetResourceResult.Ensure -eq $Ensure) + { + if ($Ensure -eq 'Present') + { + <# + Make sure default values are part of desired values if the user did + not specify them in the configuration. + #> + $desiredValues = @{ } + $PSBoundParameters + $desiredValues['Ensure'] = $Ensure + + $testTargetResourceReturnValue = Test-DscParameterState -CurrentValues $getTargetResourceResult ` + -DesiredValues $desiredValues ` + -ValuesToCheck @( + 'Ensure' + 'AuditName' + 'ApplicationRoleChangePasswordGroup' + 'AuditChangeGroup' + 'BackupRestoreGroup' + 'BrokerLoginGroup' + 'DatabaseChangeGroup' + 'DatabaseLogoutGroup' + 'DatabaseMirroringLoginGroup' + 'DatabaseObjectAccessGroup' + 'DatabaseObjectChangeGroup' + 'DatabaseObjectOwnershipChangeGroup' + 'DatabaseObjectPermissionChangeGroup' + 'DatabaseOperationGroup' + 'DatabaseOwnershipChangeGroup' + 'DatabasePermissionChangeGroup' + 'DatabasePrincipalChangeGroup' + 'DatabasePrincipalImpersonationGroup' + 'DatabaseRoleMemberChangeGroup' + 'DbccGroup' + 'FailedDatabaseAuthenticationGroup' + 'FailedLoginGroup' + 'FulltextGroup' + 'LoginChangePasswordGroup' + 'LogoutGroup' + 'SchemaObjectAccessGroup' + 'SchemaObjectChangeGroup' + 'SchemaObjectOwnershipChangeGroup' + 'SchemaObjectPermissionChangeGroup' + 'ServerObjectChangeGroup' + 'ServerObjectOwnershipChangeGroup' + 'ServerObjectPermissionChangeGroup' + 'ServerOperationGroup' + 'ServerPermissionChangeGroup' + 'ServerPrincipalChangeGroup' + 'ServerPrincipalImpersonationGroup' + 'ServerRoleMemberChangeGroup' + 'ServerStateChangeGroup' + 'SuccessfulDatabaseAuthenticationGroup' + 'SuccessfulLoginGroup' + 'TraceChangeGroup' + 'UserChangePasswordGroup' + 'UserDefinedAuditGroup' + 'TransactionGroup' + 'Enabled' + ) + } + else + { + $testTargetResourceReturnValue = $true + } + } + else + { + $testTargetResourceReturnValue = $false + } + + if ($testTargetResourceReturnValue) + { + Write-Verbose -Message $script:localizedData.InDesiredState + } + else + { + Write-Verbose -Message $script:localizedData.NotInDesiredState + } + + return $testTargetResourceReturnValue +} + +<# + .SYNOPSIS + Converts a datatable to a HashTable + + .PARAMETER DataTable + The datatable to be converted to a hashtable. + The datatable can have one or two columns. + When the datatable has one column, the hashtable wil use $true as value for the second collomn. +#> +function Convert-ToHashTable +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [system.Data.DataTable] + $DataTable + ) + $resultSet = @{ } + foreach ($Item in $DataTable) + { + if ($DataTable.Columns.Count -eq 1) + { + $resultSet.Add($Item[0], $true) + } + if ($DataSet.Columns.Count -eq 2) + { + $resultSet.Add($Item[0], $Item[1]) + } + } + return $resultSet +} + +<# + .SYNOPSIS + Disables a server audit specification. + + .PARAMETER Name + Specifies the name of the server audit specification to be disabled. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the audit exist. +#> +function Disable-AuditSpecification +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + Write-Verbose -Message ( + $script:localizedData.DisableAuditSpecification -f $Name, $serverName, $instanceName + ) + + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT SPECIFICATION [{0}] WITH (STATE = OFF);' -f $Name + ) +} + +<# + .SYNOPSIS + Enables a server audit specification. + + .PARAMETER Name + Specifies the name of the server audit specification to be enabled. + + .PARAMETER ServerName + Specifies the host name of the SQL Server on which the instance exist. + + .PARAMETER InstanceName + Specifies the SQL instance in which the audit exist. +#> +function Enable-AuditSpecification +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $ServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + Write-Verbose -Message ( + $script:localizedData.EnableAuditSpecification -f $Name, $serverName, $instanceName + ) + + $invokeQueryParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Database = 'MASTER' + } + + Invoke-Query @invokeQueryParameters -Query ( + 'ALTER SERVER AUDIT SPECIFICATION [{0}] WITH (STATE = ON);' -f $Name + ) +} + +<# + .SYNOPSIS + Converts the parameter name to the equivalent database object name. + + .PARAMETER InString + Specifies the name of the parameter to be converted to a server policy string. + + .EXAMPLE + $strKey = 'AuditChangeGroup' + $ret = Get-DatabaseObjectNameFromPSParamName -InString $strKey + $ret + Should return 'AUDIT_CHANGE_GROUP' in $ret + + .NOTES + Trough this function the capital letters in Get- Test- and Set-TargetResource + parameters get converted, e.g. FulltextGroup and AuditChangeGroup to + FULLTEXT_GROUP and AUDIT_CHANGE_GROUP. + This is the naming of audit specification within SQL Server. + + DO NOT CHANGE THE CAPITALS IN THE PARAMETERS! + These are used to find the places to insert an underscore. +#> +function Get-DatabaseObjectNameFromPSParamName +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $InString + ) + return ($InString -creplace '([A-Z\W_]|\d+)(? +function Get-AuditSpecificationMutationString +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [Hashtable] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [hashtable] + $DesiredValues + ) + $resultString = '' + $CurrentValues.GetEnumerator() | ForEach-Object { + if ($null -eq $_.Value -or $_.Value -eq '') + { + $val = 'False' + } + else + { + $val = $_.Value + } + $resultString += Test-SingleRow -CurrentKey $_.Key -CurrentValue $val -DesiredValues $DesiredValues + } + return $resultString.TrimEnd(',') +} + +<# + .SYNOPSIS + Builds a ADD/DROP string for all the needed changes of the database audit specification + + .PARAMETER $CurrentKey + Specifies the current Key to be checked against the hash DesiredValues. + + .PARAMETER $CurrentValue + Specifies the current Value to be checked against the hash DesiredValues. + + .PARAMETER $DesiredValues + Specifies the hashtable containing the desired settings. Usualy this should be all of the input parameters of Set-TargetResource. +#> +function Test-SingleRow +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $CurrentKey, + + [Parameter(Mandatory = $true)] + [String] + $CurrentValue, + + [Parameter(Mandatory = $true)] + [hashtable] + $DesiredValues + ) + + $return = '' + + if ($CurrentKey -ne 'Name' -and + $CurrentKey -ne 'ServerName' -and + $CurrentKey -ne 'InstanceName' -and + $CurrentKey -ne 'AuditName' -and + $CurrentKey -ne 'Enabled' -and + $CurrentKey -ne 'Ensure' -and + $CurrentKey -ne 'Force') + { + if ($null -eq $DesiredValues.$CurrentKey) + { + $desiredValue = 'False' + } + else + { + $desiredValue = $DesiredValues.$CurrentKey + } + + #When not equal + if ($CurrentValue -ne $desiredValue) + { + $DatabaseCompatibleKeyString = Get-DatabaseObjectNameFromPSParamName -InString $CurrentKey + + if ($desiredValue -eq 'True') + { + #When desired, add it. + $return = 'ADD ({0}),' -f $DatabaseCompatibleKeyString + } + else + { + #When not wanted, drop it. + $return = 'DROP ({0}),' -f $DatabaseCompatibleKeyString + } + } + } + + return $return +} + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof new file mode 100644 index 000000000..8aff2e0f1 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof @@ -0,0 +1,53 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlServerAuditSpecification")] +class DSC_SqlServerAuditSpecification : OMI_BaseResource +{ + [Key, Description("Specifies the host name of the SQL Server on which the instance exist.")] String ServerName; + [Key, Description("Specifies the SQL instance in which the Audit exist.")] String InstanceName; + [Key, Description("Specifies the name of the SQL audit specification to be added or removed.")] String Name; + [Write, Description("Specifies if the audit specification should be enabled. Defaults to $false")] Boolean Enabled; + [Write, Description("Specifies the audit to be used as storage.")] String AuditName; + [Write, Description("Specifies if this property should be audited.")] Boolean ApplicationRoleChangePasswordGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean AuditChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean BackupRestoreGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean BrokerLoginGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseLogoutGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseMirroringLoginGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectAccessGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectOwnershipChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectPermissionChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseOperationGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseOwnershipChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabasePermissionChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabasePrincipalChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabasePrincipalImpersonationGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseRoleMemberChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean DbccGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean FailedDatabaseAuthenticationGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean FailedLoginGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean FulltextGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean LoginChangePasswordGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean LogoutGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectAccessGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectOwnershipChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectPermissionChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerObjectChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerObjectOwnershipChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerObjectPermissionChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerOperationGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerPermissionChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerPrincipalChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerPrincipalImpersonationGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerRoleMemberChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean ServerStateChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean SuccessfulDatabaseAuthenticationGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean SuccessfulLoginGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean TraceChangeGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean UserChangePasswordGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean UserDefinedAuditGroup; + [Write, Description("Specifies if this property should be audited.")] Boolean TransactionGroup; + [Write, Description("Specifies if the audit should be present or absent. If 'Present' then the audit will be added to the server and, if needed, the audit will be updated. If 'Absent' then the audit will be removed from the server. Defaults to 'Present'."), ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure; + [Write, Description("Specifies if it is allowed to re-create the server audit when the DestinationType changes. Defaults to $false not allowing server audits to be re-created.")] Boolean Force; +}; diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 b/source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 new file mode 100644 index 000000000..d3e9c9b3b --- /dev/null +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 @@ -0,0 +1,17 @@ +ConvertFrom-StringData @' + RetrievingAuditSpecificationInformation = Retrieving information about Audit specification '{0}' from the server '{1}' instance '{2}'. (SSAS0001) + EvaluateAuditSpecification = Determining if the audit specification '{0}' on server '{1}' instance '{2}' is in the desired state. (SSAS0002) + AuditSpecificationExist = The audit specification '{0}' exist in on server '{1}' instance '{2}'. (SSAS0003) + InDesiredState = The audit specification is in desired state. (SSAS0004) + NotInDesiredState = The audit specification is not in desired state. (SSAS0005) + DisableAuditSpecification = Disabling audit audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0006) + EnableAuditSpecification = Enabling audit audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0007) + CreateAuditSpecification = Creating the audit specification '{0}' on server '{1}' instance '{2}'. (SSAS008) + FailedCreateAuditSpecification = Failed creating audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0009) + AuditAlreadyInUse = Audit {0} for audit specification '{1}' on server '{2}' instance '{3}' is already in use for audit specification '{4}' (SSAS0010) + DropAuditSpecification = Removing the audit specification '{0}' from server '{1}' instance '{2}'. (SSAS0011) + FailedDropAuditSpecification = Failed removing the audit specification '{0}' from server '{1}' instance '{2}'. (SSAS0012) + SetAuditSpecification = Setting the audit specification '{0}' on server '{1}' instance '{2}' to the desired state. (SSAS0013) + FailedUpdateAuditSpecification = Failed updating audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0014) + ForceNotEnabled = Unable to re-create the server audit. The server audit needs to be re-created but the configuration has not opt-in to re-create the audit. To opt-in set the parameter Force to $true. (SSAS0015) +'@ diff --git a/source/Examples/README.md b/source/Examples/README.md index e209a042f..58d35b729 100644 --- a/source/Examples/README.md +++ b/source/Examples/README.md @@ -15,6 +15,8 @@ These are the links to the examples for each individual resource. - [SqlAGReplica](Resources/SqlAGReplica) - [SqlAlias](Resources/SqlAlias) - [SqlAlwaysOnService](Resources/SqlAlwaysOnService) +- [SqlServerAudit](Resources/SqlServerAudit) +- [SqlServerAuditSpecification](Resources/SqlServerAuditSpecification) - [SqlDatabase](Resources/SqlDatabase) - [SqlDatabaseDefaultLocation](Resources/SqlDatabaseDefaultLocation) - [SqlDatabasePermission](Resources/SqlDatabasePermission) diff --git a/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 new file mode 100644 index 000000000..28015df6a --- /dev/null +++ b/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 @@ -0,0 +1,65 @@ +<# + .EXAMPLE + This example shows how to ensure that an audit destination + is absent on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + DestinationType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + + SqlServerAuditSpecification 'ServerAuditSpecification_AdminAudit' + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AdminAudit' + AuditName = 'SecLogAudit' + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlServerAudit]SecurityLogAudit_Server' + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 new file mode 100644 index 000000000..b5678b709 --- /dev/null +++ b/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 @@ -0,0 +1,49 @@ +<# + .EXAMPLE + This example shows how to ensure that an audit destination + is absent on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + DestinationType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + + SqlServerAuditSpecification 'ServerAuditSpecification_AdminAudit' + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AdminAudit' + AuditName = 'SecLogAudit' + Enabled = $true + DatabaseLogoutGroup = $true + FailedDatabaseAuthenticationGroup = $true + FailedLoginGroup = $true + LoginChangePasswordGroup = $true + LogoutGroup = $true + SuccessfulDatabaseAuthenticationGroup = $true + SuccessfulLoginGroup = $true + DependsOn = '[SqlServerAudit]SecurityLogAudit_Server' + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 new file mode 100644 index 000000000..982e72351 --- /dev/null +++ b/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 @@ -0,0 +1,44 @@ +<# + .EXAMPLE + This example shows how to ensure that an audit destination + is absent on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + DestinationType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + + SqlServerAuditSpecification 'ServerAuditSpecification_AuditAudit' + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AuditAudit' + AuditName = 'SecLogAudit' + Enabled = $true + AuditChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]SecurityLogAudit_Server" + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 new file mode 100644 index 000000000..d981e2dbc --- /dev/null +++ b/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 @@ -0,0 +1,92 @@ +<# + .EXAMPLE + This example shows how to add multiple audit specifications to the same instance. + Each audit can only contain one audit specification. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAudit SecurityLogAudit_Server01 + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit01' + DestinationType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + + SqlServerAudit SecurityLogAudit_Server02 + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit02' + DestinationType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential + } + + SqlServerAuditSpecification ServerAuditSpecification_AuditAudit + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AuditAudit' + AuditName = 'SecLogAudit01' + Enabled = $true + AuditChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]SecurityLogAudit_Server01" + PsDscRunAsCredential = $SqlAdministratorCredential + } + + + + SqlServerAuditSpecification ServerAuditSpecification_AdminAudit + { + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AdminAudit' + AuditName = 'SecLogAudit02' + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]SecurityLogAudit_Server02" + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 new file mode 100644 index 000000000..3a4497229 --- /dev/null +++ b/source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 @@ -0,0 +1,29 @@ +<# + .EXAMPLE + This example shows how to ensure that an audit destination + is absent on the instance sqltest.company.local\DSC. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlServerAuditSpecification 'ServerAuditSpecification_AdminAudit' + { + Ensure = 'Absent' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AdminAudit' + AuditName = 'SecLogAudit' + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 new file mode 100644 index 000000000..aa9cfa4f7 --- /dev/null +++ b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 @@ -0,0 +1,252 @@ +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (-not (Test-BuildCategory -Type 'Integration' -Category @('Integration_SQL2016','Integration_SQL2017'))) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceFriendlyName = 'SqlServerAuditSpecification' +$script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + +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 'Integration' + +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_AddAudit1_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 + $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 + $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 + $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 + $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_AddSecLogAudit_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_RemoveAudit1_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -BeNullOrEmpty + $resourceCurrentState.FilePath | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty + $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 new file mode 100644 index 000000000..730150f09 --- /dev/null +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -0,0 +1,283 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + + UserName = "$env:COMPUTERNAME\SqlAdmin" + Password = 'P@ssw0rd1' + + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQLTEST' + + AuditName1 = 'FileAudit' + DestinationType1 = 'File' + FilePath1 = 'C:\Temp\audit\' + MaximumFileSize1 = 10 + MaximumFileSizeUnit1 = 'MB' + MaximumRolloverFiles1 = 11 + + AuditSpecificationName = 'AdminAudit' + } + ) + } +} + +<# + .SYNOPSIS + Creates a Server Audit with File destination. +#> +Configuration MSFT_SqlServerAuditSpecification_AddAudit1_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_TestPrepare' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName1 + DestinationType = $Node.DestinationType1 + FilePath = $Node.FilePath1 + MaximumFileSize = $Node.MaximumFileSize1 + MaximumFileSizeUnit = $Node.MaximumFileSizeUnit1 + MaximumRolloverFiles = $Node.MaximumRolloverFiles1 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + + SqlServerAuditSpecification 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]Integration_TestPrepare" + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} + +<# + .SYNOPSIS + Creates a audit to the securitylog, with a filer. +#> +Configuration MSFT_SqlServerAudit_AddSecLogAudit_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_TestPrepare' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName2 + DestinationType = $Node.DestinationType2 + Filter = $Node.Filter2 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + + SqlServerAuditSpecification 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]Integration_TestPrepare" + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} + +<# + .SYNOPSIS + Should remove the filter +#> +Configuration MSFT_SqlServerAudit_AddSecLogAuditNoFilter_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_TestPrepare' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName2 + DestinationType = $Node.DestinationType2 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + + SqlServerAuditSpecification 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]Integration_TestPrepare" + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} + +<# + .SYNOPSIS + Removes the file audit. +#> +Configuration MSFT_SqlServerAudit_RemoveAudit1_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerAudit 'Integration_TestPrepare' + { + Ensure = 'Absent' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName1 + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + + SqlServerAuditSpecification 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = "[SqlServerAudit]Integration_TestPrepare" + PsDscRunAsCredential = $SqlAdministratorCredential + } + } +} From 461c7943b2b94d75b09b929856daae35dc2b525a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 19 Jan 2020 17:34:18 +0100 Subject: [PATCH 03/71] Add integration tests to pipeline --- azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 647d85ae9..6a2ded10d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -197,6 +197,8 @@ stages: 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' 'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' 'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' # Group 4 'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' 'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' From 4e7c1dc565d6c2f9df642f265df9e1743dcd0362 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 20 Jan 2020 16:24:56 +0100 Subject: [PATCH 04/71] Rename configurations --- tests/Integration/DSC_SqlServerAudit.config.ps1 | 8 ++++---- .../DSC_SqlServerAuditSpecification.config.ps1 | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Integration/DSC_SqlServerAudit.config.ps1 b/tests/Integration/DSC_SqlServerAudit.config.ps1 index d4c6a848a..153fb677a 100644 --- a/tests/Integration/DSC_SqlServerAudit.config.ps1 +++ b/tests/Integration/DSC_SqlServerAudit.config.ps1 @@ -44,7 +44,7 @@ else .SYNOPSIS Creates a Server Audit with File destination. #> -Configuration MSFT_SqlServerAudit_AddFileAudit_Config +Configuration DSC_SqlServerAudit_AddFileAudit_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -73,7 +73,7 @@ Configuration MSFT_SqlServerAudit_AddFileAudit_Config .SYNOPSIS Creates a audit to the securitylog, with a filer. #> -Configuration MSFT_SqlServerAudit_AddSecLogAudit_Config +Configuration DSC_SqlServerAudit_AddSecLogAudit_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -99,7 +99,7 @@ Configuration MSFT_SqlServerAudit_AddSecLogAudit_Config .SYNOPSIS Should remove the filter #> -Configuration MSFT_SqlServerAudit_AddSecLogAuditNoFilter_Config +Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -124,7 +124,7 @@ Configuration MSFT_SqlServerAudit_AddSecLogAuditNoFilter_Config .SYNOPSIS Removes the file audit. #> -Configuration MSFT_SqlServerAudit_RemoveAudit1_Config +Configuration DSC_SqlServerAudit_RemoveAudit1_Config { Import-DscResource -ModuleName 'SqlServerDsc' diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 index 730150f09..b5c701a27 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -42,7 +42,7 @@ else .SYNOPSIS Creates a Server Audit with File destination. #> -Configuration MSFT_SqlServerAuditSpecification_AddAudit1_Config +Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -106,7 +106,7 @@ Configuration MSFT_SqlServerAuditSpecification_AddAudit1_Config .SYNOPSIS Creates a audit to the securitylog, with a filer. #> -Configuration MSFT_SqlServerAudit_AddSecLogAudit_Config +Configuration DSC_SqlServerAudit_AddSecLogAudit_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -167,7 +167,7 @@ Configuration MSFT_SqlServerAudit_AddSecLogAudit_Config .SYNOPSIS Should remove the filter #> -Configuration MSFT_SqlServerAudit_AddSecLogAuditNoFilter_Config +Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -227,7 +227,7 @@ Configuration MSFT_SqlServerAudit_AddSecLogAuditNoFilter_Config .SYNOPSIS Removes the file audit. #> -Configuration MSFT_SqlServerAudit_RemoveAudit1_Config +Configuration DSC_SqlServerAudit_RemoveAudit1_Config { Import-DscResource -ModuleName 'SqlServerDsc' From cca8cf98380dc0b6213302e164544bf1a1944e1c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 3 Aug 2022 17:04:04 +0200 Subject: [PATCH 05/71] Convert integration tests to Pester 5 --- .../DSC_SqlServerAudit.Integration.Tests.ps1 | 426 ++++++++++-------- .../Integration/DSC_SqlServerAudit.config.ps1 | 16 +- ...erAuditSpecification.Integration.Tests.ps1 | 423 +++++++++-------- ...DSC_SqlServerAuditSpecification.config.ps1 | 282 ++++++------ 4 files changed, 607 insertions(+), 540 deletions(-) diff --git a/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 index 3bae5f9bd..98567a8ef 100644 --- a/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 @@ -1,251 +1,285 @@ -Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') +BeforeDiscovery { + 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.' + } -if (-not (Test-BuildCategory -Type 'Integration' -Category @('Integration_SQL2016','Integration_SQL2017'))) -{ - return + <# + Need to define that variables here to be used in the Pester Discover to + build the ForEach-blocks. + #> + $script:dscResourceFriendlyName = 'SqlServerAudit' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" } -$script:dscModuleName = 'SqlServerDsc' -$script:dscResourceFriendlyName = 'SqlServerAudit' -$script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" - -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.' -} +BeforeAll { + # Need to define the variables here which will be used in Pester Run. + $script:dscModuleName = 'SqlServerDsc' + $script:dscResourceFriendlyName = 'SqlServerAudit' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" -$script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Integration' + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' -try -{ $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" . $configFile +} + +AfterAll { + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} - Describe "$($script:dscResourceName)_Integration" { +Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019') { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddFileAudit_Config" + ) { BeforeAll { - $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + $configurationName = $_ } - $configurationName = "$($script:dscResourceName)_AddFileAudit_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + AfterAll { + Wait-ForIdleLcm + } - & $configurationName @configurationParameters + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + & $configurationName @configurationParameters - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 - $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 - $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 - $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 - $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 + $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 + $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 + $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 + $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 } - $configurationName = "$($script:dscResourceName)_AddSecLogAudit_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } - & $configurationName @configurationParameters + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddSecLogAudit_Config" + ) { + BeforeAll { + $configurationName = $_ + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + AfterAll { + Wait-ForIdleLcm + } - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + & $configurationName @configurationParameters - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 - $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw } - $configurationName = "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 + } - & $configurationName @configurationParameters + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" + ) { + BeforeAll { + $configurationName = $_ + } - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + AfterAll { + Wait-ForIdleLcm + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 - $resourceCurrentState.Filter | Should -BeNullOrEmpty - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw } - $configurationName = "$($script:dscResourceName)_RemoveAudit1_Config" + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -BeNullOrEmpty + } - & $configurationName @configurationParameters + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveAudit1_Config" + ) { + BeforeAll { + $configurationName = $_ + } - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + AfterAll { + Wait-ForIdleLcm + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' } - $resourceCurrentState.Ensure | Should -Be 'Absent' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -BeNullOrEmpty - $resourceCurrentState.FilePath | Should -BeNullOrEmpty - $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty - $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty - $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -BeNullOrEmpty + $resourceCurrentState.FilePath | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty + $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' } } } -finally -{ - Restore-TestEnvironment -TestEnvironment $script:testEnvironment -} diff --git a/tests/Integration/DSC_SqlServerAudit.config.ps1 b/tests/Integration/DSC_SqlServerAudit.config.ps1 index 153fb677a..df9780dfc 100644 --- a/tests/Integration/DSC_SqlServerAudit.config.ps1 +++ b/tests/Integration/DSC_SqlServerAudit.config.ps1 @@ -16,14 +16,14 @@ else $ConfigurationData = @{ AllNodes = @( @{ - NodeName = 'localhost' - CertificateFile = $env:DscPublicCertificatePath + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath - UserName = "$env:COMPUTERNAME\SqlAdmin" - Password = 'P@ssw0rd1' + UserName = "$env:COMPUTERNAME\SqlAdmin" + Password = 'P@ssw0rd1' - ServerName = $env:COMPUTERNAME - InstanceName = 'DSCSQLTEST' + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQLTEST' AuditName1 = 'FileAudit' DestinationType1 = 'File' @@ -71,7 +71,7 @@ Configuration DSC_SqlServerAudit_AddFileAudit_Config <# .SYNOPSIS - Creates a audit to the securitylog, with a filer. + Creates a audit to the security log, with a filter. #> Configuration DSC_SqlServerAudit_AddSecLogAudit_Config { @@ -86,7 +86,7 @@ Configuration DSC_SqlServerAudit_AddSecLogAudit_Config InstanceName = $Node.InstanceName Name = $Node.AuditName2 DestinationType = $Node.DestinationType2 - Filter = $Node.Filter2 + Filter = $Node.Filter2 PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 index aa9cfa4f7..47d7a7afc 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 @@ -1,252 +1,281 @@ -Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') +BeforeDiscovery { + 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.' + } -if (-not (Test-BuildCategory -Type 'Integration' -Category @('Integration_SQL2016','Integration_SQL2017'))) -{ - return + <# + Need to define that variables here to be used in the Pester Discover to + build the ForEach-blocks. + #> + $script:dscResourceFriendlyName = 'SqlServerAuditSpecification' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" } -$script:dscModuleName = 'SqlServerDsc' -$script:dscResourceFriendlyName = 'SqlServerAuditSpecification' -$script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" - -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.' -} +BeforeAll { + # Need to define the variables here which will be used in Pester Run. + $script:dscModuleName = 'SqlServerDsc' + $script:dscResourceFriendlyName = 'SqlServerAuditSpecification' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" -$script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Integration' + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' -try -{ $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" . $configFile +} + +Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019') { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } - Describe "$($script:dscResourceName)_Integration" { + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddAudit1_Config" + ) { BeforeAll { - $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + $configurationName = $_ } - $configurationName = "$($script:dscResourceName)_AddAudit1_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + AfterAll { + Wait-ForIdleLcm + } - & $configurationName @configurationParameters + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + & $configurationName @configurationParameters - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 - $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 - $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 - $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 - $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 + $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 + $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 + $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 + $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 } - $configurationName = "$($script:dscResourceName)_AddSecLogAudit_Config" - - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } - & $configurationName @configurationParameters + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddSecLogAudit_Config" + ) { + BeforeAll { + $configurationName = $_ + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + AfterAll { + Wait-ForIdleLcm + } - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + & $configurationName @configurationParameters - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 - $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw } - $configurationName = "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 + } - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } - & $configurationName @configurationParameters + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" + ) { + BeforeAll { + $configurationName = $_ + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + AfterAll { + Wait-ForIdleLcm + } - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + & $configurationName @configurationParameters - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' } - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 - $resourceCurrentState.Filter | Should -BeNullOrEmpty - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw } - $configurationName = "$($script:dscResourceName)_RemoveAudit1_Config" + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.Filter | Should -BeNullOrEmpty + } - Context ('When using configuration {0}' -f $configurationName) { - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } - & $configurationName @configurationParameters + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveAudit1_Config" + ) { + BeforeAll { + $configurationName = $_ + } - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } + AfterAll { + Wait-ForIdleLcm + } - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } + & $configurationName @configurationParameters - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' } - $resourceCurrentState.Ensure | Should -Be 'Absent' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -BeNullOrEmpty - $resourceCurrentState.FilePath | Should -BeNullOrEmpty - $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty - $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty - $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty - } + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DestinationType | Should -BeNullOrEmpty + $resourceCurrentState.FilePath | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty + $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty + $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' } } } -finally -{ - Restore-TestEnvironment -TestEnvironment $script:testEnvironment -} - diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 index b5c701a27..52a84ad76 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -16,21 +16,21 @@ else $ConfigurationData = @{ AllNodes = @( @{ - NodeName = 'localhost' - CertificateFile = $env:DscPublicCertificatePath + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath - UserName = "$env:COMPUTERNAME\SqlAdmin" - Password = 'P@ssw0rd1' + UserName = "$env:COMPUTERNAME\SqlAdmin" + Password = 'P@ssw0rd1' - ServerName = $env:COMPUTERNAME - InstanceName = 'DSCSQLTEST' + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQLTEST' - AuditName1 = 'FileAudit' - DestinationType1 = 'File' - FilePath1 = 'C:\Temp\audit\' - MaximumFileSize1 = 10 - MaximumFileSizeUnit1 = 'MB' - MaximumRolloverFiles1 = 11 + AuditName1 = 'FileAudit' + DestinationType1 = 'File' + FilePath1 = 'C:\Temp\audit\' + MaximumFileSize1 = 10 + MaximumFileSizeUnit1 = 'MB' + MaximumRolloverFiles1 = 11 AuditSpecificationName = 'AdminAudit' } @@ -67,37 +67,38 @@ Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config SqlServerAuditSpecification 'Integration_Test' { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]Integration_TestPrepare" - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlServerAudit]Integration_TestPrepare' + + PsDscRunAsCredential = $SqlAdministratorCredential } } } @@ -119,46 +120,47 @@ Configuration DSC_SqlServerAudit_AddSecLogAudit_Config InstanceName = $Node.InstanceName Name = $Node.AuditName2 DestinationType = $Node.DestinationType2 - Filter = $Node.Filter2 + Filter = $Node.Filter2 PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } - SqlServerAuditSpecification 'Integration_Test' + SqlServerAuditSpecification 'Integration_Test' { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]Integration_TestPrepare" - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlServerAudit]Integration_TestPrepare' + + PsDscRunAsCredential = $SqlAdministratorCredential } } } @@ -186,39 +188,40 @@ Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } - SqlServerAuditSpecification 'Integration_Test' + SqlServerAuditSpecification 'Integration_Test' { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]Integration_TestPrepare" - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlServerAudit]Integration_TestPrepare' + + PsDscRunAsCredential = $SqlAdministratorCredential } } } @@ -247,37 +250,38 @@ Configuration DSC_SqlServerAudit_RemoveAudit1_Config SqlServerAuditSpecification 'Integration_Test' { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]Integration_TestPrepare" - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditSpecificationName + AuditName = $Node.AuditName1 + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlServerAudit]Integration_TestPrepare' + + PsDscRunAsCredential = $SqlAdministratorCredential } } } From d9418d75be596c2dd62d3c05a7b34240f2afd3c0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 3 Aug 2022 17:40:18 +0200 Subject: [PATCH 06/71] Style changes --- .vscode/settings.json | 4 +- .../DSC_SqlServerAudit.psm1 | 75 ++++---- .../DSC_SqlServerAuditSpecification.psm1 | 165 ++++++++++-------- 3 files changed, 134 insertions(+), 110 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ea3c72550..6b806e216 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -67,7 +67,9 @@ "xSQLAOGroupEnsure", "Test-SPDSCObjectHasProperty", "DynamicAlloc", - "GetxPDTVariable" + "GetxPDTVariable", + "Dbcc", + "creplace" ], "cSpell.ignorePaths": [ ".git" diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 index 7c0c69f77..0a0324438 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 @@ -127,7 +127,7 @@ function Get-TargetResource This can be CONTINUE, FAIL_OPERATION or SHUTDOWN. .PARAMETER QueueDelay - Specifies the maximum delay before a event is writen to the store. + Specifies the maximum delay before a event is written to the store. When set to low this could impact server performance. When set to high events could be missing when a server crashes. @@ -146,6 +146,7 @@ function Get-TargetResource #> function Set-TargetResource { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is used which calls the command Connect-Sql')] [CmdletBinding()] param ( @@ -230,15 +231,15 @@ function Set-TargetResource } if ($FilePath) { - $FilePath = $FilePath.Trimend('\') + '\' + $FilePath = $FilePath.TrimEnd('\') + '\' #Test if audit file location exists, and create if it does not. if (-not (Test-Path -Path $FilePath)) { Write-Verbose -Message ( - $script:localizedData.CreateFolder -f $FilePath.Trimend('\') + $script:localizedData.CreateFolder -f $FilePath.TrimEnd('\') ) - New-Item -ItemType directory -Path $FilePath.Trimend('\') + New-Item -ItemType directory -Path $FilePath.TrimEnd('\') } } @@ -325,7 +326,7 @@ function Set-TargetResource WithPart = $withPart } - #if curent audit state is enabled, disable it before edit. + # If current audit state is enabled, disable it before edit. if ($getTargetResourceResult.Enabled -eq $true) { Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName @@ -340,7 +341,8 @@ function Set-TargetResource $errorMessage = $script:localizedData.FailedUpdateAudit -f $Name, $ServerName, $InstanceName New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } - #if audit state was disabled for edit, Re-enable it. + + # If audit state was disabled for edit, Re-enable it. if ($getTargetResourceResult.Enabled -eq $true) { Enable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName @@ -349,9 +351,9 @@ function Set-TargetResource else { <# - Current server audit has a diferent storage type, the - server audit needs to be re-created. - #> + Current server audit has a different storage type, the + server audit needs to be re-created. + #> Write-Verbose -Message ( $script:localizedData.ChangingAuditDestinationType -f $Name, @@ -363,7 +365,6 @@ function Set-TargetResource $recreateAudit = $true } - } } @@ -383,7 +384,7 @@ function Set-TargetResource $script:localizedData.DropAudit -f $Name, $serverName, $instanceName ) - #if curent audit state is enabled, disable it before removal. + #if current audit state is enabled, disable it before removal. if ($getTargetResourceResult.Enabled -eq $true) { Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName @@ -488,7 +489,7 @@ function Set-TargetResource $script:localizedData.AddFilter -f $Filter, $Name, $serverName, $instanceName ) - #if curent audit state is enabled, disable it before setting filter. + #if current audit state is enabled, disable it before setting filter. if ($getTargetResourceResult.Enabled -eq $true) { Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName @@ -565,7 +566,7 @@ function Set-TargetResource This can be CONTINUE, FAIL_OPERATION or SHUTDOWN. .PARAMETER QueueDelay - Specifies the maximum delay before a event is writen to the store. + Specifies the maximum delay before a event is written to the store. When set to low this could impact server performance. When set to high events could be missing when a server crashes. @@ -584,6 +585,7 @@ function Set-TargetResource #> function Test-TargetResource { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-TargetResource is called')] [CmdletBinding()] [OutputType([System.Boolean])] [CmdletBinding()] @@ -665,8 +667,8 @@ function Test-TargetResource #sanitize user input. if ($FilePath) { - $FilePath = $FilePath.Trimend('\') + '\' - $PSBoundParameters['FilePath'] = $FilePath.Trimend('\') + '\' + $FilePath = $FilePath.TrimEnd('\') + '\' + $PSBoundParameters['FilePath'] = $FilePath.TrimEnd('\') + '\' } $TargetResourceParameters = @{ @@ -689,29 +691,34 @@ function Test-TargetResource $desiredValues = @{ } + $PSBoundParameters $desiredValues['Ensure'] = $Ensure - $testTargetResourceReturnValue = Test-DscParameterState -CurrentValues $getTargetResourceResult ` - -DesiredValues $desiredValues ` - -ValuesToCheck @( - 'FilePath' - 'MaximumFileSize' - 'MaximumFileSizeUnit' - 'QueueDelay' - 'OnFailure' - 'Enabled' - 'Ensure' - 'DestinationType' - 'MaximumFiles' - 'MaximumRolloverFiles' - 'ReserveDiskSpace' - 'Filter' - ) + $testDscParameterStateParameters = @{ + CurrentValues = $getTargetResourceResult + DesiredValues = $desiredValues + ValuesToCheck = @( + 'FilePath' + 'MaximumFileSize' + 'MaximumFileSizeUnit' + 'QueueDelay' + 'OnFailure' + 'Enabled' + 'Ensure' + 'DestinationType' + 'MaximumFiles' + 'MaximumRolloverFiles' + 'ReserveDiskSpace' + 'Filter' + ) + } + + $testTargetResourceReturnValue = Test-DscParameterState @testDscParameterStateParameters + <# WORKAROUND for possible bug? Test-DscParameterState does not see if a parameter is removed as parameter but still exists in the DSC resource. - When in desired state do some aditional tests. - When not in desired state, aditional testing is not needed. + When in desired state do some additional tests. + When not in desired state, additional testing is not needed. #> if ($testTargetResourceReturnValue) { @@ -895,5 +902,3 @@ function Set-ServerAudit $WithPart ) } - -Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 index 19dd40a0f..a2e1bc63d 100644 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 @@ -26,6 +26,7 @@ $script:localizedData = Get-LocalizedData -ResourceName 'DSC_SqlServerAuditSpeci #> function Get-TargetResource { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is used which calls the command Connect-Sql')] [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param @@ -101,8 +102,6 @@ function Get-TargetResource Enabled = $false } - $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - # Default parameters for the cmdlet Invoke-Query used throughout. $invokeQueryParameters = @{ ServerName = $ServerName @@ -110,7 +109,7 @@ function Get-TargetResource Database = 'MASTER' } - $DataSetAudit = Invoke-Query @invokeQueryParameters -WithResults -Query ( + $dataSetAudit = Invoke-Query @invokeQueryParameters -WithResults -Query ( 'Select s.server_specification_id, s.is_state_enabled, @@ -121,24 +120,24 @@ function Get-TargetResource where s.name = ''{0}''' -f $Name) - if ($null -ne $DataSetAudit -and $DataSetAudit.Tables[0].Rows.Count -gt 0) + if ($null -ne $dataSetAudit -and $dataSetAudit.Tables[0].Rows.Count -gt 0) { Write-Verbose -Message ( $script:localizedData.AuditSpecificationExist -f $Name, $ServerName, $InstanceName ) - $dataSetRow = $DataSetAudit.Tables[0].Rows[0] + $dataSetRow = $dataSetAudit.Tables[0].Rows[0] - $DataSetAuditSpecification = Invoke-Query @invokeQueryParameters -WithResults -Query ( + $dataSetAuditSpecification = Invoke-Query @invokeQueryParameters -WithResults -Query ( 'Select audit_action_name from sys.server_audit_specification_details where server_specification_id = {0}' -f $dataSetRow.server_specification_id) - #this should always happen!!!!! - if ($null -ne $DataSetAuditSpecification -and $DataSetAuditSpecification.Tables.Count -gt 0) + # This should always happen! + if ($null -ne $dataSetAuditSpecification -and $dataSetAuditSpecification.Tables.Count -gt 0) { - $resultSet = Convert-ToHashTable -DataTable $DataSetAuditSpecification.Tables[0] + $resultSet = Convert-ToHashTable -DataTable $dataSetAuditSpecification.Tables[0] $returnValue['Ensure'] = 'Present' $returnValue['AuditName'] = $dataSetRow.auditName @@ -187,6 +186,7 @@ function Get-TargetResource $returnValue['Enabled'] = $dataSetRow.is_state_enabled } } + return $returnValue } @@ -345,6 +345,7 @@ function Get-TargetResource #> function Set-TargetResource { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is used which calls the command Connect-Sql')] [CmdletBinding()] param ( @@ -569,6 +570,7 @@ function Set-TargetResource $desiredValues = @{ } + $PSBoundParameters $auditSpecificationAddDropString = Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues + Write-Verbose -Message $( Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues ) @@ -679,7 +681,7 @@ function Set-TargetResource New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } - # Create, if needed and posible. + # Create, if needed and possible. try { Invoke-Query @invokeQueryParameters -Query ( @@ -858,6 +860,7 @@ function Set-TargetResource #> function Test-TargetResource { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is called when Get-TargetResource is called')] [CmdletBinding()] [OutputType([System.Boolean])] param @@ -1084,55 +1087,58 @@ function Test-TargetResource $desiredValues = @{ } + $PSBoundParameters $desiredValues['Ensure'] = $Ensure - $testTargetResourceReturnValue = Test-DscParameterState -CurrentValues $getTargetResourceResult ` - -DesiredValues $desiredValues ` - -ValuesToCheck @( - 'Ensure' - 'AuditName' - 'ApplicationRoleChangePasswordGroup' - 'AuditChangeGroup' - 'BackupRestoreGroup' - 'BrokerLoginGroup' - 'DatabaseChangeGroup' - 'DatabaseLogoutGroup' - 'DatabaseMirroringLoginGroup' - 'DatabaseObjectAccessGroup' - 'DatabaseObjectChangeGroup' - 'DatabaseObjectOwnershipChangeGroup' - 'DatabaseObjectPermissionChangeGroup' - 'DatabaseOperationGroup' - 'DatabaseOwnershipChangeGroup' - 'DatabasePermissionChangeGroup' - 'DatabasePrincipalChangeGroup' - 'DatabasePrincipalImpersonationGroup' - 'DatabaseRoleMemberChangeGroup' - 'DbccGroup' - 'FailedDatabaseAuthenticationGroup' - 'FailedLoginGroup' - 'FulltextGroup' - 'LoginChangePasswordGroup' - 'LogoutGroup' - 'SchemaObjectAccessGroup' - 'SchemaObjectChangeGroup' - 'SchemaObjectOwnershipChangeGroup' - 'SchemaObjectPermissionChangeGroup' - 'ServerObjectChangeGroup' - 'ServerObjectOwnershipChangeGroup' - 'ServerObjectPermissionChangeGroup' - 'ServerOperationGroup' - 'ServerPermissionChangeGroup' - 'ServerPrincipalChangeGroup' - 'ServerPrincipalImpersonationGroup' - 'ServerRoleMemberChangeGroup' - 'ServerStateChangeGroup' - 'SuccessfulDatabaseAuthenticationGroup' - 'SuccessfulLoginGroup' - 'TraceChangeGroup' - 'UserChangePasswordGroup' - 'UserDefinedAuditGroup' - 'TransactionGroup' - 'Enabled' - ) + $testDscParameterStateParameters = @{ + CurrentValues = $getTargetResourceResult + DesiredValues = $desiredValues + ValuesToCheck = @( + 'Ensure' + 'AuditName' + 'ApplicationRoleChangePasswordGroup' + 'AuditChangeGroup' + 'BackupRestoreGroup' + 'BrokerLoginGroup' + 'DatabaseChangeGroup' + 'DatabaseLogoutGroup' + 'DatabaseMirroringLoginGroup' + 'DatabaseObjectAccessGroup' + 'DatabaseObjectChangeGroup' + 'DatabaseObjectOwnershipChangeGroup' + 'DatabaseObjectPermissionChangeGroup' + 'DatabaseOperationGroup' + 'DatabaseOwnershipChangeGroup' + 'DatabasePermissionChangeGroup' + 'DatabasePrincipalChangeGroup' + 'DatabasePrincipalImpersonationGroup' + 'DatabaseRoleMemberChangeGroup' + 'DbccGroup' + 'FailedDatabaseAuthenticationGroup' + 'FailedLoginGroup' + 'FulltextGroup' + 'LoginChangePasswordGroup' + 'LogoutGroup' + 'SchemaObjectAccessGroup' + 'SchemaObjectChangeGroup' + 'SchemaObjectOwnershipChangeGroup' + 'SchemaObjectPermissionChangeGroup' + 'ServerObjectChangeGroup' + 'ServerObjectOwnershipChangeGroup' + 'ServerObjectPermissionChangeGroup' + 'ServerOperationGroup' + 'ServerPermissionChangeGroup' + 'ServerPrincipalChangeGroup' + 'ServerPrincipalImpersonationGroup' + 'ServerRoleMemberChangeGroup' + 'ServerStateChangeGroup' + 'SuccessfulDatabaseAuthenticationGroup' + 'SuccessfulLoginGroup' + 'TraceChangeGroup' + 'UserChangePasswordGroup' + 'UserDefinedAuditGroup' + 'TransactionGroup' + 'Enabled' + ) + } + $testTargetResourceReturnValue = Test-DscParameterState @testDscParameterStateParameters } else { @@ -1158,12 +1164,12 @@ function Test-TargetResource <# .SYNOPSIS - Converts a datatable to a HashTable + Converts a data table to a HashTable .PARAMETER DataTable - The datatable to be converted to a hashtable. - The datatable can have one or two columns. - When the datatable has one column, the hashtable wil use $true as value for the second collomn. + The data table to be converted to a hashtable. The data table can have one + or two columns. When the data table has one column, the hashtable will use + $true as value for the second column. #> function Convert-ToHashTable { @@ -1175,16 +1181,18 @@ function Convert-ToHashTable [system.Data.DataTable] $DataTable ) - $resultSet = @{ } - foreach ($Item in $DataTable) + + $resultSet = @{} + + foreach ($item in $DataTable) { if ($DataTable.Columns.Count -eq 1) { - $resultSet.Add($Item[0], $true) + $resultSet.Add($item[0], $true) } if ($DataSet.Columns.Count -eq 2) { - $resultSet.Add($Item[0], $Item[1]) + $resultSet.Add($item[0], $item[1]) } } return $resultSet @@ -1219,6 +1227,7 @@ function Disable-AuditSpecification [System.String] $Name ) + Write-Verbose -Message ( $script:localizedData.DisableAuditSpecification -f $Name, $serverName, $instanceName ) @@ -1263,6 +1272,7 @@ function Enable-AuditSpecification [System.String] $Name ) + Write-Verbose -Message ( $script:localizedData.EnableAuditSpecification -f $Name, $serverName, $instanceName ) @@ -1309,18 +1319,22 @@ function Get-DatabaseObjectNameFromPSParamName [System.String] $InString ) + return ($InString -creplace '([A-Z\W_]|\d+)(? function Get-AuditSpecificationMutationString { @@ -1336,7 +1350,9 @@ function Get-AuditSpecificationMutationString [hashtable] $DesiredValues ) + $resultString = '' + $CurrentValues.GetEnumerator() | ForEach-Object { if ($null -eq $_.Value -or $_.Value -eq '') { @@ -1348,12 +1364,14 @@ function Get-AuditSpecificationMutationString } $resultString += Test-SingleRow -CurrentKey $_.Key -CurrentValue $val -DesiredValues $DesiredValues } + return $resultString.TrimEnd(',') } <# .SYNOPSIS - Builds a ADD/DROP string for all the needed changes of the database audit specification + Builds a ADD/DROP string for all the needed changes of the database audit + specification .PARAMETER $CurrentKey Specifies the current Key to be checked against the hash DesiredValues. @@ -1362,7 +1380,8 @@ function Get-AuditSpecificationMutationString Specifies the current Value to be checked against the hash DesiredValues. .PARAMETER $DesiredValues - Specifies the hashtable containing the desired settings. Usualy this should be all of the input parameters of Set-TargetResource. + Specifies the hashtable containing the desired settings. Usually this + should be all of the input parameters of Set-TargetResource. #> function Test-SingleRow { @@ -1422,5 +1441,3 @@ function Test-SingleRow return $return } - -Export-ModuleMember -Function *-TargetResource From e230971d9c5f3433c98a818c7a894db03fd9519c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 3 Aug 2022 17:42:19 +0200 Subject: [PATCH 07/71] Fix azure-pipelines.yml --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6a2ded10d..51ddffece 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -87,6 +87,7 @@ stages: - task: PowerShell@2 name: test displayName: 'Run HQRM Test' + condition: succeededOrFailed() inputs: filePath: './build.ps1' arguments: '-Tasks hqrmtest' From d7ebf809c253025b068648c21a95e50ac1541c1a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 3 Aug 2022 19:59:52 +0200 Subject: [PATCH 08/71] Fix localization command --- .../DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 | 10 +++++----- .../DSC_SqlServerAuditSpecification.psm1 | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 index 0a0324438..a47f53e03 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 @@ -1,10 +1,10 @@ -$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent -$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' +$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common' +$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' -$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'SqlServerDsc.Common' -Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'SqlServerDsc.Common.psm1') +Import-Module -Name $script:sqlServerDscHelperModulePath +Import-Module -Name $script:resourceHelperModulePath -$script:localizedData = Get-LocalizedData -ResourceName 'DSC_SqlServerAudit' +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' <# diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 index a2e1bc63d..8114668e8 100644 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 @@ -1,10 +1,10 @@ -$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent -$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' +$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common' +$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' -$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'SqlServerDsc.Common' -Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'SqlServerDsc.Common.psm1') +Import-Module -Name $script:sqlServerDscHelperModulePath +Import-Module -Name $script:resourceHelperModulePath -$script:localizedData = Get-LocalizedData -ResourceName 'DSC_SqlServerAuditSpecification' +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' <# .SYNOPSIS From 29ec1fb5ba222b71d47c4278ce8b009ee3acb30b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 3 Aug 2022 20:59:08 +0200 Subject: [PATCH 09/71] Fix integ test --- .../DSC_SqlServerAuditSpecification.config.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 index 52a84ad76..1190364ac 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -105,9 +105,9 @@ Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config <# .SYNOPSIS - Creates a audit to the securitylog, with a filer. + Creates a audit to the security log, with a filer. #> -Configuration DSC_SqlServerAudit_AddSecLogAudit_Config +Configuration DSC_SqlServerAuditSpecification_AddSecLogAudit_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -120,7 +120,7 @@ Configuration DSC_SqlServerAudit_AddSecLogAudit_Config InstanceName = $Node.InstanceName Name = $Node.AuditName2 DestinationType = $Node.DestinationType2 - Filter = $Node.Filter2 + Filter = $Node.Filter2 PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` @@ -169,7 +169,7 @@ Configuration DSC_SqlServerAudit_AddSecLogAudit_Config .SYNOPSIS Should remove the filter #> -Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config +Configuration DSC_SqlServerAuditSpecification_AddSecLogAuditNoFilter_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -230,7 +230,7 @@ Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config .SYNOPSIS Removes the file audit. #> -Configuration DSC_SqlServerAudit_RemoveAudit1_Config +Configuration DSC_SqlServerAuditSpecification_RemoveAudit1_Config { Import-DscResource -ModuleName 'SqlServerDsc' @@ -250,7 +250,7 @@ Configuration DSC_SqlServerAudit_RemoveAudit1_Config SqlServerAuditSpecification 'Integration_Test' { - Ensure = 'Present' + Ensure = 'Absent' ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditSpecificationName From 655a1d7906329cf3d349e7ffd58c4ca06e42bb71 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 4 Aug 2022 10:23:01 +0200 Subject: [PATCH 10/71] Fix SqlAudit --- .../DSC_SqlServerAudit.psm1 | 112 +- .../DSC_SqlServerAudit.schema.mof | 6 +- tests/Unit/DSC_SqlServerAudit.Tests.ps1 | 1144 ++++++++++------- 3 files changed, 765 insertions(+), 497 deletions(-) diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 index a47f53e03..1c9042f3d 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 @@ -6,7 +6,6 @@ Import-Module -Name $script:resourceHelperModulePath $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' - <# .SYNOPSIS Returns the current state of the audit on a server. @@ -19,6 +18,12 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' .PARAMETER InstanceName Specifies the SQL instance in which the audit exists. + + .PARAMETER DestinationType + Specifies the location where the audit should write to. + This can be File, SecurityLog or ApplicationLog. + + Not used in Get-TargetResource. #> function Get-TargetResource { @@ -36,7 +41,12 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] + [System.String] + $DestinationType ) $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName @@ -109,6 +119,9 @@ function Get-TargetResource .PARAMETER FilePath Specifies the location where te log files wil be placed. + .PARAMETER Filter + Specifies the filter that should be used on the audit. + .PARAMETER MaximumFiles Specifies the number of files on disk. @@ -143,10 +156,14 @@ function Get-TargetResource then the audit will be added to the server and, if needed, the audit will be updated. If 'Absent' then the audit will be removed from the server. Defaults to 'Present'. + + .PARAMETER Force + Specifies if it is allowed to re-create the server audit when the DestinationType + changes. Defaults to $false not allowing server audits to be re-created. #> function Set-TargetResource { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is used which calls the command Connect-Sql')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'The command Invoke-Query is used which calls the command Connect-Sql')] [CmdletBinding()] param ( @@ -162,10 +179,10 @@ function Set-TargetResource [System.String] $InstanceName, - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] [System.String] - $DestinationType = 'SecurityLog', + $DestinationType, [Parameter()] [System.String] @@ -216,7 +233,7 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $Force = $false + $Force ) Write-Verbose -Message ( @@ -548,6 +565,9 @@ function Set-TargetResource .PARAMETER FilePath Specifies the location where te log files wil be placed. + .PARAMETER Filter + Specifies the filter that should be used on the audit. + .PARAMETER MaximumFiles Specifies the number of files on disk. @@ -582,10 +602,14 @@ function Set-TargetResource then the audit will be added to the server and, if needed, the audit will be updated. If 'Absent' then the audit will be removed from the server. Defaults to 'Present'. + + .PARAMETER Force + Specifies if it is allowed to re-create the server audit when the DestinationType + changes. Defaults to $false not allowing server audits to be re-created. #> function Test-TargetResource { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-TargetResource is called')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'The command Connect-Sql is called when Get-TargetResource is called')] [CmdletBinding()] [OutputType([System.Boolean])] [CmdletBinding()] @@ -603,10 +627,10 @@ function Test-TargetResource [System.String] $InstanceName, - [Parameter()] + [Parameter(Mandatory = $true)] [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] [System.String] - $DestinationType = 'SecurityLog', + $DestinationType, [Parameter()] [System.String] @@ -657,7 +681,7 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $Force = $false + $Force ) Write-Verbose -Message ( @@ -680,57 +704,47 @@ function Test-TargetResource # Get-TargetResource will also help us to test if the audit exist. $getTargetResourceResult = Get-TargetResource @TargetResourceParameters + $testTargetResourceReturnValue = $true + if ($getTargetResourceResult.Ensure -eq $Ensure) { if ($Ensure -eq 'Present') { - <# - Make sure default values are part of desired values if the user did - not specify them in the configuration. - #> - $desiredValues = @{ } + $PSBoundParameters - $desiredValues['Ensure'] = $Ensure - $testDscParameterStateParameters = @{ - CurrentValues = $getTargetResourceResult - DesiredValues = $desiredValues - ValuesToCheck = @( - 'FilePath' - 'MaximumFileSize' - 'MaximumFileSizeUnit' - 'QueueDelay' - 'OnFailure' - 'Enabled' + CurrentValues = $getTargetResourceResult + DesiredValues = $PSBoundParameters + ExcludeProperties = @( + 'Name' + 'ServerName' + 'InstanceName' + 'Force' + # Ensure was already evaluated prior, no need to check it again. 'Ensure' - 'DestinationType' - 'MaximumFiles' - 'MaximumRolloverFiles' - 'ReserveDiskSpace' - 'Filter' ) } - $testTargetResourceReturnValue = Test-DscParameterState @testDscParameterStateParameters + $propertiesNotInDesiredState = Compare-DscParameterState @testDscParameterStateParameters - <# - WORKAROUND for possible bug? - Test-DscParameterState does not see if a parameter is removed as parameter - but still exists in the DSC resource. - - When in desired state do some additional tests. - When not in desired state, additional testing is not needed. - #> - if ($testTargetResourceReturnValue) + if ($propertiesNotInDesiredState) { - if ($getTargetResourceResult.Filter -ne $Filter) - { - $testTargetResourceReturnValue = $false - } + $testTargetResourceReturnValue = $false } - } - else - { - $testTargetResourceReturnValue = $true + + # <# + # WORKAROUND for possible bug? + # Test-DscParameterState does not see if a parameter is removed as parameter + # but still exists in the DSC resource. + + # When in desired state do some additional tests. + # When not in desired state, additional testing is not needed. + # #> + # if ($testTargetResourceReturnValue) + # { + # if ($getTargetResourceResult.Filter -ne $Filter) + # { + # $testTargetResourceReturnValue = $false + # } + # } } } else diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof index 8b3ec0301..5c6c5d92c 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof @@ -4,9 +4,9 @@ class DSC_SqlServerAudit : OMI_BaseResource [Key, Description("Specifies the host name of the SQL Server on which the instance exist.")] String ServerName; [Key, Description("Specifies the SQL instance in which the Audit exist.")] String InstanceName; [Key, Description("Specifies the name of the SQL audit to be added or removed.")] String Name; - [Write, Description("Specifies the storage type of the server audit."), ValueMap{"File", "SecurityLog", "ApplicationLog"}, Values{"File", "SecurityLog", "ApplicationLog"}] String DestinationType; - [Write, Description("Specifies the path where the audit files are stored when DestinationType is set to File.")] String FilePath; - [Write, Description("Specifies if the audit should be enabled. Defaults to $false")] Boolean Enabled; + [Required, Description("Specifies the location where the audit should write to."), ValueMap{"File", "SecurityLog", "ApplicationLog"}, Values{"File", "SecurityLog", "ApplicationLog"}] String DestinationType; + [Write, Description("Specifies the path where the audit files are stored when DestinationType is set to `File`.")] String FilePath; + [Write, Description("Specifies if the audit should be enabled. Defaults to `$false`.")] Boolean Enabled; [Write, Description("Specifies the filter that should be used on the audit.")] String Filter; [Write, Description("Specifies the number of file a file audit can have.")] UInt32 MaximumFiles; [Write, Description("Specifies the maximum file size of an audit file")] UInt32 MaximumFileSize; diff --git a/tests/Unit/DSC_SqlServerAudit.Tests.ps1 b/tests/Unit/DSC_SqlServerAudit.Tests.ps1 index 928012a53..1d9762679 100644 --- a/tests/Unit/DSC_SqlServerAudit.Tests.ps1 +++ b/tests/Unit/DSC_SqlServerAudit.Tests.ps1 @@ -1,33 +1,39 @@ <# .SYNOPSIS - Automated unit test for DSC_SqlServerAudit DSC resource. - - .NOTES - To run this script locally, please make sure to first run the bootstrap - script. Read more at - https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment + Unit test for DSC_SqlAudit 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_SqlServerAudit' +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +# Suppressing this rule because tests are mocking passwords in clear text. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () -function Invoke-TestSetup -{ +BeforeDiscovery { try { - Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + 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.' + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + $script:dscResourceName = 'DSC_SqlServerAudit' $script:testEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -35,99 +41,119 @@ function Invoke-TestSetup -ResourceType 'Mof' ` -TestType 'Unit' + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + # Loading mocked classes Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') - # Load the default SQL Module stub - Import-SQLModuleStub + # Load the correct SQL Module stub + $script:stubModuleName = Import-SQLModuleStub -PassThru + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName } -function Invoke-TestCleanup -{ +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + Restore-TestEnvironment -TestEnvironment $script:testEnvironment + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscResourceName -All | Remove-Module -Force + + # Unload the stub module. + Remove-SqlModuleStub -Name $script:stubModuleName + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force } -Invoke-TestSetup - -# Begin Testing -try -{ - Invoke-TestSetup - - InModuleScope $script:dscResourceName { - $mockServerName = 'SERVER01' - $mockInstanceName = 'INSTANCE' - $mockAuditName = 'FileAudit' - - # TODO: Update type and get the correct values for this. - $mockAuditObject = New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAuditName -PassThru | - Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | - Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force - - $mockConnectSql = { - return New-Object -TypeName Object | - Add-Member -MemberType ScriptProperty -Name 'Audits' -Value { - return @( - @{ - $mockAuditName = $mockAuditObject - } - ) - } -PassThru -Force +Describe 'SqlAudit\Get-TargetResource' -Tag 'Get' { + BeforeAll { + InModuleScope -ScriptBlock { + # Default parameters that are used for the It-blocks. + $script:mockDefaultParameters = @{ + InstanceName = 'MSSQLSERVER' + ServerName = 'localhost' + Name = 'FileAudit' + # TODO: Remove this + Verbose = $true + } } + } - $defaultParameters = @{ - InstanceName = $mockInstanceName - ServerName = $mockServerName - Name = $mockAuditName - Verbose = $true + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockGetTargetResourceParameters = $script:mockDefaultParameters.Clone() } + } - Describe 'DSC_SqlServerAudit\Get-TargetResource' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When the audit is present' { BeforeAll { - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + Mock -CommandName Connect-SQL -MockWith { + return @( + ( + New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{ + # TODO: get the correct values for this. + 'FileAudit' = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'FileAudit' -PassThru | + Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | + Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force + } + } -PassThru -Force + ) + ) + } } - Context 'When the system is not in the desired state' { - BeforeEach { - $testParameters = $defaultParameters.Clone() - $testParameters.Name = 'UnknownAudit' + It 'Should call the mock function Connect-SQL' { + InModuleScope -ScriptBlock { + { Get-TargetResource @mockGetTargetResourceParameters } | Should -Not -Throw } - It 'Should call the mock function Connect-SQL' { - { Get-TargetResource @testParameters } | Should -Not -Throw - - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + } - It 'Should return the desired state as absent' { - $result = Get-TargetResource @testParameters + It 'Should return the desired state as present' { + InModuleScope -ScriptBlock { + $result = Get-TargetResource @mockGetTargetResourceParameters - $result.Ensure | Should -Be 'Absent' - $result.Enabled | Should -BeFalse + $result.Ensure | Should -Be 'Present' + $result.Enabled | Should -BeTrue } + } - It 'Should return the same values as passed as parameters' { - $result = Get-TargetResource @testParameters + It 'Should return the same values as passed as parameters' { + InModuleScope -ScriptBlock { + $result = Get-TargetResource @mockGetTargetResourceParameters - $result.Name | Should -Be $testParameters.Name - $result.ServerName | Should -Be $testParameters.ServerName - $result.InstanceName | Should -Be $testParameters.InstanceName + $result.Name | Should -Be $mockGetTargetResourceParameters.Name + $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName + $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName } + } - It 'Should return $null for the rest of the properties' { - $result = Get-TargetResource @testParameters + It 'Should return the correct value for the rest of the properties' { + InModuleScope -ScriptBlock { + $result = Get-TargetResource @mockGetTargetResourceParameters - $result.DestinationType | Should -BeNullOrEmpty + # TODO: get the correct values for this. + $result.DestinationType | Should -Be 'File' $result.FilePath | Should -BeNullOrEmpty $result.Filter | Should -BeNullOrEmpty $result.MaximumFiles | Should -BeNullOrEmpty @@ -135,41 +161,79 @@ try $result.MaximumFileSizeUnit | Should -BeNullOrEmpty $result.MaximumRolloverFiles | Should -BeNullOrEmpty $result.OnFailure | Should -BeNullOrEmpty - $result.QueueDelay | Should BeNullOrEmpty + $result.QueueDelay | Should -BeNullOrEmpty $result.ReserveDiskSpace | Should -BeNullOrEmpty } } + } - Context 'When the system is in the desired state' { - BeforeEach { - $testParameters = $defaultParameters.Clone() + Context 'When the audit is absent' { + BeforeAll { + Mock -CommandName Connect-SQL -MockWith { + return @( + ( + New-Object -TypeName Object | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{ + 'FileAudit' = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'FileAudit' -PassThru | + Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | + Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force + } + } -PassThru -Force + ) + ) } + } - It 'Should call the mock function Connect-SQL' { - { Get-TargetResource @testParameters } | Should -Not -Throw + BeforeEach { + InModuleScope -ScriptBlock { + $mockGetTargetResourceParameters.Name = 'UnknownName' + } + } - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It + It 'Should call the mock function Connect-SQL' { + InModuleScope -ScriptBlock { + { Get-TargetResource @mockGetTargetResourceParameters } | Should -Not -Throw } - It 'Should return the desired state as present' { - $result = Get-TargetResource @testParameters + Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + } - $result.Ensure | Should -Be 'Present' - $result.Enabled | Should -BeTrue + It 'Should return the desired state as present' { + InModuleScope -ScriptBlock { + $result = Get-TargetResource @mockGetTargetResourceParameters + + $result.Ensure | Should -Be 'Absent' + $result.Enabled | Should -BeFalse } + } - It 'Should return the same values as passed as parameters' { - $result = Get-TargetResource @testParameters + It 'Should return the same values as passed as parameters' { + InModuleScope -ScriptBlock { + $result = Get-TargetResource @mockGetTargetResourceParameters - $result.Name | Should -Be $testParameters.Name - $result.ServerName | Should -Be $testParameters.ServerName - $result.InstanceName | Should -Be $testParameters.InstanceName + $result.Name | Should -Be $mockGetTargetResourceParameters.Name + $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName + $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName } + } - It 'Should return the correct value for the rest of the properties' { - $result = Get-TargetResource @testParameters + It 'Should return the correct value for the rest of the properties' { + InModuleScope -ScriptBlock { + $result = Get-TargetResource @mockGetTargetResourceParameters - $result.DestinationType | Should -Be 'File' + # TODO: get the correct values for this. + $result.DestinationType | Should -BeNullOrEmpty $result.FilePath | Should -BeNullOrEmpty $result.Filter | Should -BeNullOrEmpty $result.MaximumFiles | Should -BeNullOrEmpty @@ -177,386 +241,576 @@ try $result.MaximumFileSizeUnit | Should -BeNullOrEmpty $result.MaximumRolloverFiles | Should -BeNullOrEmpty $result.OnFailure | Should -BeNullOrEmpty - $result.QueueDelay | Should BeNullOrEmpty + $result.QueueDelay | Should -BeNullOrEmpty $result.ReserveDiskSpace | Should -BeNullOrEmpty } } } + } +} - # Describe 'DSC_SqlServerAudit\Test-TargetResource' -Tag 'Test' { - # BeforeEach { - # $testParameters = $defaultParameters.Clone() +Describe 'SqlAudit\Test-TargetResource' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + # Default parameters that are used for the It-blocks. + $script:mockDefaultParameters = @{ + InstanceName = 'MSSQLSERVER' + ServerName = 'localhost' + Name = 'FileAudit' + # TODO: Remove this + Verbose = $true + } + } + } - # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - # } + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockTestTargetResourceParameters = $script:mockDefaultParameters.Clone() + } + } - # Context 'When the system is not in the desired state' { - # # Make sure the mock does not return the correct endpoint - # $mockDynamicEndpointName = $mockOtherEndpointName + Context 'When the system is in the desired state' { + Context 'When the audit is present' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = 'FileAudit' + ServerName = 'localhost' + InstanceName = 'MSSQLSERVER' + DestinationType = 'File' + FilePath = $null + Filter = $null + MaximumFiles = $null + MaximumFileSize = $null + MaximumFileSizeUnit = $null + MaximumRolloverFiles = $null + OnFailure = $null + QueueDelay = $null + ReserveDiskSpace = $null + Enabled = $false + } + } + } - # It 'Should return that desired state is absent when wanted desired state is to be Present (using default values)' { - # $testParameters.Add('Ensure', 'Present') + It 'Should return the property as in desired state' -ForEach @( + # TODO: add all properties + @{ + MockPropertyName = 'DestinationType' + MockExpectedValue = 'File' + } + ) { + InModuleScope -Parameters $_ -ScriptBlock { + $mockTestTargetResourceParameters.$MockPropertyName = $MockExpectedValue - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false + $result = Test-TargetResource @mockTestTargetResourceParameters - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } + $result | Should -BeTrue + } + } + } + + Context 'When the audit is absent' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + Name = 'FileAudit' + ServerName = 'localhost' + InstanceName = 'MSSQLSERVER' + DestinationType = $null + FilePath = $null + Filter = $null + MaximumFiles = $null + MaximumFileSize = $null + MaximumFileSizeUnit = $null + MaximumRolloverFiles = $null + OnFailure = $null + QueueDelay = $null + ReserveDiskSpace = $null + Enabled = $false + } + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + $mockTestTargetResourceParameters.Ensure = 'Absent' + } + } + + It 'Should return the property as in desired state' { + InModuleScope -ScriptBlock { + $result = Test-TargetResource @mockTestTargetResourceParameters - # It 'Should return that desired state is absent when wanted desired state is to be Present (setting all parameters)' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) + $result | Should -BeTrue + } + } + } + } +} - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false + # $mockServerName = 'SERVER01' + # $mockInstanceName = 'INSTANCE' + # $mockAuditName = 'FileAudit' + + # $mockAuditObject = New-Object -TypeName Object | + # Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAuditName -PassThru | + # Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | + # Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | + # Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force + + # $mockConnectSql = { + # return New-Object -TypeName Object | + # Add-Member -MemberType ScriptProperty -Name 'Audits' -Value { + # return @( + # @{ + # $mockAuditName = $mockAuditObject + # } + # ) + # } -PassThru -Force + # } + + # $defaultParameters = @{ + # InstanceName = $mockInstanceName + # ServerName = $mockServerName + # Name = $mockAuditName + # Verbose = $true + # } + + # BeforeAll { + # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + # } + + # Context 'When the system is not in the desired state' { + # BeforeEach { + # $testParameters = $defaultParameters.Clone() + # $testParameters.Name = 'UnknownAudit' + # } + + # It 'Should call the mock function Connect-SQL' { + # { Get-TargetResource @testParameters } | Should -Not -Throw + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # It 'Should return the desired state as absent' { + # $result = Get-TargetResource @testParameters + + # $result.Ensure | Should -Be 'Absent' + # $result.Enabled | Should -BeFalse + # } + + # It 'Should return the same values as passed as parameters' { + # $result = Get-TargetResource @testParameters + + # $result.Name | Should -Be $testParameters.Name + # $result.ServerName | Should -Be $testParameters.ServerName + # $result.InstanceName | Should -Be $testParameters.InstanceName + # } + + # It 'Should return $null for the rest of the properties' { + # $result = Get-TargetResource @testParameters + + # $result.DestinationType | Should -BeNullOrEmpty + # $result.FilePath | Should -BeNullOrEmpty + # $result.Filter | Should -BeNullOrEmpty + # $result.MaximumFiles | Should -BeNullOrEmpty + # $result.MaximumFileSize | Should -BeNullOrEmpty + # $result.MaximumFileSizeUnit | Should -BeNullOrEmpty + # $result.MaximumRolloverFiles | Should -BeNullOrEmpty + # $result.OnFailure | Should -BeNullOrEmpty + # $result.QueueDelay | Should BeNullOrEmpty + # $result.ReserveDiskSpace | Should -BeNullOrEmpty + # } + # } + + # Context 'When the system is in the desired state' { + # BeforeEach { + # $testParameters = $defaultParameters.Clone() + # } + + + # } + # } + + # Describe 'DSC_SqlServerAudit\Test-TargetResource' -Tag 'Test' { + # BeforeEach { + # $testParameters = $defaultParameters.Clone() + + # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + # } + + # Context 'When the system is not in the desired state' { + # # Make sure the mock does not return the correct endpoint + # $mockDynamicEndpointName = $mockOtherEndpointName + + # It 'Should return that desired state is absent when wanted desired state is to be Present (using default values)' { + # $testParameters.Add('Ensure', 'Present') + + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } + # It 'Should return that desired state is absent when wanted desired state is to be Present (setting all parameters)' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) - # # Make sure the mock do return the correct endpoint - # $mockDynamicEndpointName = $mockEndpointName + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false - # It 'Should return that desired state is absent when wanted desired state is to be Absent' { - # $testParameters.Add('Ensure', 'Absent') + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false + # # Make sure the mock do return the correct endpoint + # $mockDynamicEndpointName = $mockEndpointName - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } + # It 'Should return that desired state is absent when wanted desired state is to be Absent' { + # $testParameters.Add('Ensure', 'Absent') - # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener port - # $mockDynamicEndpointName = $mockEndpointName - # $mockDynamicEndpointListenerPort = $mockOtherEndpointListenerPort + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false - # Context 'When listener port is not in desired state' { - # It 'Should return that desired state is absent' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false + # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener port + # $mockDynamicEndpointName = $mockEndpointName + # $mockDynamicEndpointListenerPort = $mockOtherEndpointListenerPort - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } + # Context 'When listener port is not in desired state' { + # It 'Should return that desired state is absent' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) - # # Make sure the mock do return the correct endpoint listener port - # $mockDynamicEndpointListenerPort = $mockEndpointListenerPort + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false - # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener IP address - # $mockDynamicEndpointName = $mockEndpointName - # $mockDynamicEndpointListenerIpAddress = $mockOtherEndpointListenerIpAddress + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } - # Context 'When listener IP address is not in desired state' { - # It 'Should return that desired state is absent' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # # Make sure the mock do return the correct endpoint listener port + # $mockDynamicEndpointListenerPort = $mockEndpointListenerPort + # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener IP address + # $mockDynamicEndpointName = $mockEndpointName + # $mockDynamicEndpointListenerIpAddress = $mockOtherEndpointListenerIpAddress - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false + # Context 'When listener IP address is not in desired state' { + # It 'Should return that desired state is absent' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } - # # Make sure the mock do return the correct endpoint listener IP address - # $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false - # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint owner - # $mockDynamicEndpointName = $mockEndpointName - # $mockDynamicEndpointOwner = $mockOtherEndpointOwner + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } - # Context 'When listener Owner is not in desired state' { - # It 'Should return that desired state is absent' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Owner', $mockEndpointOwner) + # # Make sure the mock do return the correct endpoint listener IP address + # $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress + # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint owner + # $mockDynamicEndpointName = $mockEndpointName + # $mockDynamicEndpointOwner = $mockOtherEndpointOwner - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false + # Context 'When listener Owner is not in desired state' { + # It 'Should return that desired state is absent' { + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Owner', $mockEndpointOwner) - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } - # # Make sure the mock do return the correct endpoint owner - # $mockDynamicEndpointOwner = $mockEndpointOwner - # } + # $result = Test-TargetResource @testParameters + # $result | Should -Be $false - # Context 'When the system is in the desired state' { - # # Make sure the mock do return the correct endpoint - # $mockDynamicEndpointName = $mockEndpointName + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } - # It 'Should return that desired state is present when wanted desired state is to be Present (using default values)' { - # $result = Test-TargetResource @testParameters - # $result | Should -Be $true + # # Make sure the mock do return the correct endpoint owner + # $mockDynamicEndpointOwner = $mockEndpointOwner + # } - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } + # Context 'When the system is in the desired state' { + # # Make sure the mock do return the correct endpoint + # $mockDynamicEndpointName = $mockEndpointName - # # Make sure the mock does not return the correct endpoint - # $mockDynamicEndpointName = $mockOtherEndpointName + # It 'Should return that desired state is present when wanted desired state is to be Present (using default values)' { + # $result = Test-TargetResource @testParameters + # $result | Should -Be $true - # It 'Should return that desired state is present when wanted desired state is to be Absent' { - # $testParameters.Add('Ensure', 'Absent') + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } - # $result = Test-TargetResource @testParameters - # $result | Should -Be $true + # # Make sure the mock does not return the correct endpoint + # $mockDynamicEndpointName = $mockOtherEndpointName - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } + # It 'Should return that desired state is present when wanted desired state is to be Absent' { + # $testParameters.Add('Ensure', 'Absent') - # Assert-VerifiableMock - # } + # $result = Test-TargetResource @testParameters + # $result | Should -Be $true - # Describe 'DSC_SqlServerAudit\Set-TargetResource' -Tag 'Set' { - # BeforeEach { - # $testParameters = $defaultParameters.Clone() + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + # } - # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - # Mock -CommandName New-Object -MockWith $mockNewObjectEndPoint -ParameterFilter $mockNewObjectEndPoint_ParameterFilter -Verifiable - # } - - # Context 'When the system is not in the desired state' { - # # Make sure the mock do return the correct endpoint - # $mockDynamicEndpointName = $mockEndpointName - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Create() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call the method Create when desired state is to be Present (using default values)' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Absent' - # } - # } -Verifiable - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $true - # $script:mockMethodStartRan | Should -Be $true - # $script:mockMethodAlterRan | Should -Be $false - # $script:mockMethodDropRan | Should -Be $false - - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Create() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call the method Create when desired state is to be Present (setting all parameters)' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Absent' - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $true - # $script:mockMethodStartRan | Should -Be $true - # $script:mockMethodAlterRan | Should -Be $false - # $script:mockMethodDropRan | Should -Be $false - - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Drop() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call the method Drop when desired state is to be Absent' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Absent') - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $false - # $script:mockMethodDropRan | Should -Be $true - - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Alter() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call Alter method when listener port is not in desired state' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockOtherEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $true - # $script:mockMethodDropRan | Should -Be $false - - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Alter() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call Alter method when listener IP address is not in desired state' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockOtherEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $true - # $script:mockMethodDropRan | Should -Be $false - - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Alter() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call Alter method when Owner is not in desired state' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockOtherEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $true - # $script:mockMethodDropRan | Should -Be $false - - # Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Make sure the mock does not return the correct endpoint - # $mockDynamicEndpointName = $mockOtherEndpointName - - # Context 'When endpoint is missing when Ensure is set to Present' { - # It 'Should throw the correct error' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # Owner = $mockEndpointOwner - # } - # } -Verifiable - - # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) - # } - # } - - # Context 'When endpoint is missing when Ensure is set to Absent' { - # It 'Should throw the correct error' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # Owner = $mockEndpointOwner - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Absent') - - # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) - # } - # } - - # Context 'When Connect-SQL returns nothing' { - # It 'Should throw the correct error' { - # Mock -CommandName Get-TargetResource -Verifiable - # Mock -CommandName Connect-SQL -MockWith { - # return $null - # } - - # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.NotConnectedToInstance -f $testParameters.ServerName, $testParameters.InstanceName) - # } - # } - # } - - - # Assert-VerifiableMock - # } - } -} -finally -{ - Invoke-TestCleanup -} + # Assert-VerifiableMock + # } + + # Describe 'DSC_SqlServerAudit\Set-TargetResource' -Tag 'Set' { + # BeforeEach { + # $testParameters = $defaultParameters.Clone() + + # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + # Mock -CommandName New-Object -MockWith $mockNewObjectEndPoint -ParameterFilter $mockNewObjectEndPoint_ParameterFilter -Verifiable + # } + + # Context 'When the system is not in the desired state' { + # # Make sure the mock do return the correct endpoint + # $mockDynamicEndpointName = $mockEndpointName + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Create() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call the method Create when desired state is to be Present (using default values)' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Absent' + # } + # } -Verifiable + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $true + # $script:mockMethodStartRan | Should -Be $true + # $script:mockMethodAlterRan | Should -Be $false + # $script:mockMethodDropRan | Should -Be $false + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Create() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call the method Create when desired state is to be Present (setting all parameters)' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Absent' + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $true + # $script:mockMethodStartRan | Should -Be $true + # $script:mockMethodAlterRan | Should -Be $false + # $script:mockMethodDropRan | Should -Be $false + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Drop() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call the method Drop when desired state is to be Absent' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Absent') + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $false + # $script:mockMethodDropRan | Should -Be $true + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Alter() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call Alter method when listener port is not in desired state' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockOtherEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $true + # $script:mockMethodDropRan | Should -Be $false + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Alter() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call Alter method when listener IP address is not in desired state' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockOtherEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $true + # $script:mockMethodDropRan | Should -Be $false + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Set all method call tests variables to $false + # $script:mockMethodCreateRan = $false + # $script:mockMethodStartRan = $false + # $script:mockMethodAlterRan = $false + # $script:mockMethodDropRan = $false + + # # Set what the expected endpoint name should be when Alter() method is called. + # $mockExpectedNameWhenCallingMethod = $mockEndpointName + + # It 'Should call Alter method when Owner is not in desired state' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Present') + # $testParameters.Add('Port', $mockEndpointListenerPort) + # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + # $testParameters.Add('Owner', $mockOtherEndpointOwner) + + # { Set-TargetResource @testParameters } | Should -Not -Throw + # $script:mockMethodCreateRan | Should -Be $false + # $script:mockMethodStartRan | Should -Be $false + # $script:mockMethodAlterRan | Should -Be $true + # $script:mockMethodDropRan | Should -Be $false + + # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It + # } + + # # Make sure the mock does not return the correct endpoint + # $mockDynamicEndpointName = $mockOtherEndpointName + + # Context 'When endpoint is missing when Ensure is set to Present' { + # It 'Should throw the correct error' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # Owner = $mockEndpointOwner + # } + # } -Verifiable + + # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) + # } + # } + + # Context 'When endpoint is missing when Ensure is set to Absent' { + # It 'Should throw the correct error' { + # Mock -CommandName Get-TargetResource -MockWith { + # return @{ + # Ensure = 'Present' + # Port = $mockEndpointListenerPort + # IpAddress = $mockEndpointListenerIpAddress + # Owner = $mockEndpointOwner + # } + # } -Verifiable + + # $testParameters.Add('Ensure', 'Absent') + + # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) + # } + # } + + # Context 'When Connect-SQL returns nothing' { + # It 'Should throw the correct error' { + # Mock -CommandName Get-TargetResource -Verifiable + # Mock -CommandName Connect-SQL -MockWith { + # return $null + # } + + # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.NotConnectedToInstance -f $testParameters.ServerName, $testParameters.InstanceName) + # } + # } + # } + + + # Assert-VerifiableMock + # } +#} From 4d70935cb4e61fa69fc046754686e2af45b78748 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 4 Aug 2022 10:28:41 +0200 Subject: [PATCH 11/71] Fix AppVeyor --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 307bb7a40..240adef78 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -86,6 +86,8 @@ test_script: 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' #'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' + #'tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1' + #'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' ## Group 4 #'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' From c352b1843d06b062364d0a8d797e0247738bf9d2 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 4 Aug 2022 10:29:47 +0200 Subject: [PATCH 12/71] DEBUG1 --- appveyor.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 240adef78..ae722affc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,7 +19,7 @@ environment: matrix: # DEBUG: Comment and un-comment the different SQL Server version that should be tested. - TEST_CONFIGURATION: Integration_SQL2016 - - TEST_CONFIGURATION: Integration_SQL2017 + #- TEST_CONFIGURATION: Integration_SQL2017 #- TEST_CONFIGURATION: Integration_SQL2019 # DEBUG: See section on_finish last in this file on how to block build to keep RDP open. @@ -74,7 +74,7 @@ test_script: #'tests/Integration/DSC_SqlLogin.Integration.Tests.ps1' #'tests/Integration/DSC_SqlEndpoint.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseMail.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' + #'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseDefaultLocation.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1' #'tests/Integration/DSC_SqlAlwaysOnService.Integration.Tests.ps1' @@ -83,10 +83,10 @@ test_script: #'tests/Integration/DSC_SqlAgentFailsafe.Integration.Tests.ps1' ## Group 3 #'tests/Integration/DSC_SqlRole.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' + #'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' #'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' - #'tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1' #'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' ## Group 4 #'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' @@ -107,5 +107,5 @@ deploy: off # DEBUG: Un-comment the following line so that build worker is kept up all of the 60 minutes. on_finish: - ps: | - #$blockRdp = $true + $blockRdp = $true iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From c45bb4424d4231e79ebda2a8882efd6c9f1e249b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 4 Aug 2022 12:51:38 +0200 Subject: [PATCH 13/71] Fix Invoke-SqlDscQuery --- CHANGELOG.md | 1 + .../DSC_SqlServerAudit.psm1 | 5 +- source/Public/Invoke-SqlDscQuery.ps1 | 112 ++++++++++++++ .../Unit/Public/Invoke-SqlDscQuery.Tests.ps1 | 146 ++++++++++++++++++ 4 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 source/Public/Invoke-SqlDscQuery.ps1 create mode 100644 tests/Unit/Public/Invoke-SqlDscQuery.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a8101f2fd..9f520bdd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ConvertTo-SqlDscServerPermission` - `Get-SqlDscServerPermission` - `Set-SqlDscServerPermission` + - `Invoke-SqlDscQuery` - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - Add new resource SqlServerAudit. diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 index 1c9042f3d..d3292b56c 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 @@ -246,6 +246,7 @@ function Set-TargetResource $errorMessage = $script:localizedData.ImpossibleFileCombination New-InvalidOperationException -Message $errorMessage } + if ($FilePath) { $FilePath = $FilePath.TrimEnd('\') + '\' @@ -695,14 +696,14 @@ function Test-TargetResource $PSBoundParameters['FilePath'] = $FilePath.TrimEnd('\') + '\' } - $TargetResourceParameters = @{ + $getTargetResourceParameters = @{ ServerName = $ServerName InstanceName = $InstanceName Name = $Name } # Get-TargetResource will also help us to test if the audit exist. - $getTargetResourceResult = Get-TargetResource @TargetResourceParameters + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters $testTargetResourceReturnValue = $true diff --git a/source/Public/Invoke-SqlDscQuery.ps1 b/source/Public/Invoke-SqlDscQuery.ps1 new file mode 100644 index 000000000..221c18420 --- /dev/null +++ b/source/Public/Invoke-SqlDscQuery.ps1 @@ -0,0 +1,112 @@ +<# + .SYNOPSIS + Executes a query on the specified database. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specify the name of the database to execute the query on. + + .PARAMETER Query + The query string to execute. + + .PARAMETER PassThru + Specifies if the command should return any result the query might return. + + .PARAMETER StatementTimeout + Set the query StatementTimeout in seconds. Default 600 seconds (10 minutes). + + .PARAMETER RedactText + One or more strings to redact from the query when verbose messages are + written to the console. Strings here will be escaped so they will not + be interpreted as regular expressions (RegEx). + + .OUTPUTS + `[System.Data.DataSet]` when passing parameter **PassThru**, otherwise + outputs none. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Invoke-SqlDscQuery -ServerObject $serverInstance -Database master ` + -Query 'SELECT name FROM sys.databases' -PassThru + + Runs the query and returns all the database names in the instance. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Invoke-SqlDscQuery -ServerObject $serverInstance -Database master ` + -Query 'RESTORE DATABASE [NorthWinds] WITH RECOVERY' + + Runs the query to restores the database NorthWinds. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Invoke-SqlDscQuery -ServerObject $serverInstance -Database master ` + -Query "select * from MyTable where password = 'PlaceholderPa\ssw0rd1' and password = 'placeholder secret passphrase'" ` + -PassThru -RedactText @('PlaceholderPa\sSw0rd1','Placeholder Secret PassPhrase') ` + -Verbose + + Shows how to redact sensitive information in the query when the query string + is output as verbose information when the parameter Verbose is used. + + .NOTES + This is a wrapper for private function Invoke-Query, until it move into + this public function. +#> +function Invoke-SqlDscQuery +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType([System.Data.DataSet])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Query, + + [Parameter()] + [Switch] + $PassThru, + + [Parameter()] + [ValidateNotNull()] + [System.Int32] + $StatementTimeout = 600, + + [Parameter()] + [System.String[]] + $RedactText + ) + + $invokeQueryParameters = @{ + SqlServerObject = $ServerObject + Database = $DatabaseName + Query = $Query + } + + if ($PSBoundParameters.ContainsKey('PassThru')) + { + $invokeQueryParameters.WithResults = $PassThru + } + + if ($PSBoundParameters.ContainsKey('StatementTimeout')) + { + $invokeQueryParameters.StatementTimeout = $StatementTimeout + } + + if ($PSBoundParameters.ContainsKey('RedactText')) + { + $invokeQueryParameters.RedactText = $RedactText + } + + return (Invoke-Query @invokeQueryParameters) +} diff --git a/tests/Unit/Public/Invoke-SqlDscQuery.Tests.ps1 b/tests/Unit/Public/Invoke-SqlDscQuery.Tests.ps1 new file mode 100644 index 000000000..9533cca2e --- /dev/null +++ b/tests/Unit/Public/Invoke-SqlDscQuery.Tests.ps1 @@ -0,0 +1,146 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Invoke-SqlDscQuery' -Tag 'Public' { + Context 'When calling the command with only mandatory parameters' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + + Mock -CommandName Invoke-Query + } + + It 'Should execute the query without throwing and without returning any result' { + $result = Invoke-SqlDscQuery -ServerObject $mockServerObject -DatabaseName 'master' -Query 'select name from sys.databases' + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-Query -ParameterFilter { + $PesterBoundParameters.Keys -contains 'SqlServerObject' -and + $PesterBoundParameters.Keys -contains 'Database' -and + $PesterBoundParameters.Keys -contains 'Query' + } -Exactly -Times 1 -Scope It + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should execute the query without throwing and without returning any result' { + $result = $mockServerObject | Invoke-SqlDscQuery -DatabaseName 'master' -Query 'select name from sys.databases' + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-Query -ParameterFilter { + $PesterBoundParameters.Keys -contains 'SqlServerObject' -and + $PesterBoundParameters.Keys -contains 'Database' -and + $PesterBoundParameters.Keys -contains 'Query' + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When calling the command with optional parameter StatementTimeout' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + + Mock -CommandName Invoke-Query + } + + It 'Should execute the query without throwing and without returning any result' { + $result = Invoke-SqlDscQuery -StatementTimeout 900 -ServerObject $mockServerObject -DatabaseName 'master' -Query 'select name from sys.databases' + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-Query -ParameterFilter { + $PesterBoundParameters.Keys -contains 'SqlServerObject' -and + $PesterBoundParameters.Keys -contains 'Database' -and + $PesterBoundParameters.Keys -contains 'Query' -and + $PesterBoundParameters.Keys -contains 'StatementTimeout' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When calling the command with optional parameter RedactText' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + + Mock -CommandName Invoke-Query + } + + It 'Should execute the query without throwing and without returning any result' { + $result = Invoke-SqlDscQuery -RedactText @('MyString') -ServerObject $mockServerObject -DatabaseName 'master' -Query 'select name from sys.databases' + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-Query -ParameterFilter { + $PesterBoundParameters.Keys -contains 'SqlServerObject' -and + $PesterBoundParameters.Keys -contains 'Database' -and + $PesterBoundParameters.Keys -contains 'Query' -and + $PesterBoundParameters.Keys -contains 'RedactText' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When calling the command with optional parameter PassThru' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + + # Actual testing that Invoke-Query returns values is done in the unit tests for Invoke-Query. + Mock -CommandName Invoke-Query + } + + It 'Should execute the query without throwing and without returning any result' { + $result = Invoke-SqlDscQuery -PassThru -ServerObject $mockServerObject -DatabaseName 'master' -Query 'select name from sys.databases' + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-Query -ParameterFilter { + $PesterBoundParameters.Keys -contains 'SqlServerObject' -and + $PesterBoundParameters.Keys -contains 'Database' -and + $PesterBoundParameters.Keys -contains 'Query' -and + $PesterBoundParameters.Keys -contains 'WithResults' + } -Exactly -Times 1 -Scope It + } + } +} From b8f74d871e18e1343311a51808f679210a97e1cc Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 4 Aug 2022 15:06:48 +0200 Subject: [PATCH 14/71] Fix New-SqlDscAudit --- source/Public/New-SqlDscAudit.ps1 | 201 ++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 source/Public/New-SqlDscAudit.ps1 diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 new file mode 100644 index 000000000..516634591 --- /dev/null +++ b/source/Public/New-SqlDscAudit.ps1 @@ -0,0 +1,201 @@ +<# + .SYNOPSIS + Creates a server audit. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the server audit to be added. + + .PARAMETER Filter + Specifies the filter that should be used on the audit. + + .PARAMETER OnFailure + Specifies what should happen when writing events to the store fails. + This can be 'Continue', 'FailOperation', or 'Shutdown'. + + .PARAMETER QueueDelay + Specifies the maximum delay before a event is written to the store. + When set to low this could impact server performance. + When set to high events could be missing when a server crashes. + + .PARAMETER Type + Specifies the log location where the audit should write to. + This can be SecurityLog or ApplicationLog. + + .PARAMETER FilePath + Specifies the location where te log files wil be placed. + + .PARAMETER ReserveDiskSpace + Specifies if the needed file space should be reserved. only needed + when writing to a file log. + + .PARAMETER MaximumFiles + Specifies the number of files on disk. + + .PARAMETER MaximumFileSize + Specifies the maximum file size in units by parameter MaximumFileSizeUnit. + + .PARAMETER MaximumFileSizeUnit + Specifies the unit that is used for the file size. this can be KB, MB or GB. + + .PARAMETER MaximumRolloverFiles + Specifies the amount of files on disk before SQL Server starts reusing + the files. If not specified then it is set to unlimited. + + .OUTPUTS + None. + + .NOTES + TODO: Update comment-based help from here: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql?view=sql-server-ver16 +#> +function New-SqlDscAudit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType()] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $Filter, + + # TODO:Maybe not use default it the parameter inte not necessary? + [Parameter()] + [ValidateSet('Continue', 'FailOperation', 'Shutdown')] + [System.String] + $OnFailure = 'Continue', + + # TODO:Maybe not use default it the parameter inte not necessary? + [Parameter()] + [System.UInt32] + $QueueDelay = 1000, + + [Parameter(ParameterSetName = 'Log', Mandatory = $true)] + [ValidateSet('SecurityLog', 'ApplicationLog')] + #[ValidateSet('File', 'SecurityLog', 'ApplicationLog')] + [System.String] + $Type, + + [Parameter(ParameterSetName = 'File', Mandatory = $true)] + [System.String] + $FilePath, + + [Parameter(ParameterSetName = 'File')] + [System.Management.Automation.SwitchParameter] + $ReserveDiskSpace, + + [Parameter(ParameterSetName = 'File')] + [System.UInt32] + $MaximumFiles, + + # TODO:Maybe not use default it the parameter inte not necessary? + [Parameter(ParameterSetName = 'File')] + [System.UInt32] + $MaximumFileSize = 10, + + # TODO:Maybe not use default it the parameter inte not necessary? + [Parameter(ParameterSetName = 'File')] + [ValidateSet('KB', 'MB', 'GB')] + [System.String] + $MaximumFileSizeUnit = 'MB', + + [Parameter(ParameterSetName = 'File')] + [System.UInt32] + $MaximumRolloverFiles + ) + + switch ($PSCmdlet.ParameterSetName) + { + 'Log' + { + # Translate the value for Type. + $queryType = ( + @{ + SecurityLog = 'SECURITY_LOG' + ApplicationLog = 'APPLICATION_LOG' + } + ).$Type + } + + 'File' + { + $queryType = 'FILE' + } + } + + $query = 'CREATE SERVER AUDIT [{0}] TO {1}' -f $Name, $queryType + + # Translate the value for OnFailure. + $queryOnFailure = ( + @{ + Continue = 'CONTINUE' + FailOperation = 'FAIL_OPERATION' + ShutDown = 'SHUTDOWN' + } + ).$OnFailure + + # 'File' + # { + # $strReserveDiskSpace = 'OFF' + # if ($ReserveDiskSpace) + # { + # $strReserveDiskSpace = 'ON' + # } + + # $strFiles = '' + # if ($MaximumFiles) + # { + # $strFiles = 'MAX_FILES = {0},' -f $MaximumFiles + # } + # if ($MaximumRolloverFiles) + # { + # TODO: If not passed then UNLIMITED should be used (it is default when not adding MAX_ROLLOVER_FILES to the query). + # $strFiles = 'MAX_ROLLOVER_FILES = {0},' -f $MaximumRolloverFiles + # } + + # # TODO: Should handle Filter (WHERE-clause) + + # $target = 'FILE ( + # FILEPATH = N''{0}'', + # MAXSIZE = {1} {2}, + # {3} + # RESERVE_DISK_SPACE = {4} )' -f + # $FilePath, + # $MaximumFileSize, + # $MaximumFileSizeUnit, + # $strFiles, + # $strReserveDiskSpace + # } + + # $withPart = 'QUEUE_DELAY = {0}, ON_FAILURE = {1}' -f @( + # $QueueDelay, + # $queryOnFailure + # ) + + # $invokeQueryParameters = @{ + # ServerName = $ServerName + # InstanceName = $InstanceName + # Database = 'MASTER' + # } + + Invoke-Query @invokeQueryParameters -Query $query + + # 'CREATE SERVER AUDIT [{0}] TO {1} + # WITH ( + # {2} + # );' -f + # $Name, + # $Target, + # $WithPart + # ) +} From d67842fb268fe8060fa1ee7a0f3db42b378940b3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 5 Aug 2022 14:16:22 +0200 Subject: [PATCH 15/71] Fix New-SqlDscAudit --- source/Public/Invoke-SqlDscQuery.ps1 | 1 + source/Public/New-SqlDscAudit.ps1 | 345 +++++++++++++----- source/en-US/SqlServerDsc.strings.psd1 | 9 +- tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 385 ++++++++++++++++++++ 4 files changed, 649 insertions(+), 91 deletions(-) create mode 100644 tests/Unit/Public/New-SqlDscAudit.Tests.ps1 diff --git a/source/Public/Invoke-SqlDscQuery.ps1 b/source/Public/Invoke-SqlDscQuery.ps1 index 221c18420..4f687e043 100644 --- a/source/Public/Invoke-SqlDscQuery.ps1 +++ b/source/Public/Invoke-SqlDscQuery.ps1 @@ -58,6 +58,7 @@ function Invoke-SqlDscQuery { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [OutputType([System.Data.DataSet])] + # TODO: Should use ShouldProcess [CmdletBinding()] param ( diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index 516634591..55428f41e 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -9,7 +9,8 @@ Specifies the name of the server audit to be added. .PARAMETER Filter - Specifies the filter that should be used on the audit. + Specifies the filter that should be used on the audit. See [predicate expression](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql) + how to write the syntax for the filter. .PARAMETER OnFailure Specifies what should happen when writing events to the store fails. @@ -20,6 +21,14 @@ When set to low this could impact server performance. When set to high events could be missing when a server crashes. + .PARAMETER AuditGuid + Specifies the GUID found in the mirrored database. To support scenarios such + as database mirroring an audit needs a specific GUID. + + .PARAMETER OperatorAudit + Specifies if auditing will capture Microsoft support engineers operations + during support requests. Applies to Azure SQL Managed Instance only. + .PARAMETER Type Specifies the log location where the audit should write to. This can be SecurityLog or ApplicationLog. @@ -48,16 +57,22 @@ None. .NOTES - TODO: Update comment-based help from here: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql?view=sql-server-ver16 + See the SQL Server documentation: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql #> function New-SqlDscAudit { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] [OutputType()] - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'Log', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'File', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'FileWithSize', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'FileWithMaxFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'FileWithMaxRolloverFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxRolloverFiles', Mandatory = $true, ValueFromPipeline = $true)] [Microsoft.SqlServer.Management.Smo.Server] $ServerObject, @@ -69,16 +84,28 @@ function New-SqlDscAudit [System.String] $Filter, - # TODO:Maybe not use default it the parameter inte not necessary? [Parameter()] [ValidateSet('Continue', 'FailOperation', 'Shutdown')] [System.String] - $OnFailure = 'Continue', + $OnFailure, - # TODO:Maybe not use default it the parameter inte not necessary? [Parameter()] + [ValidateRange(1000, 2147483647)] [System.UInt32] - $QueueDelay = 1000, + $QueueDelay, + + [Parameter()] + [ValidatePattern('^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] + [System.String] + $AuditGuid, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $OperatorAudit, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, [Parameter(ParameterSetName = 'Log', Mandatory = $true)] [ValidateSet('SecurityLog', 'ApplicationLog')] @@ -87,41 +114,66 @@ function New-SqlDscAudit $Type, [Parameter(ParameterSetName = 'File', Mandatory = $true)] - [System.String] - $FilePath, + [Parameter(ParameterSetName = 'FileWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [ValidateScript({ + if (-not (Test-Path -Path $_)) + { + throw ($script:localizedData.Audit_PathParameterValueInvalid -f $_) + } - [Parameter(ParameterSetName = 'File')] - [System.Management.Automation.SwitchParameter] - $ReserveDiskSpace, + return $true + })] + [System.String] + $Path, - [Parameter(ParameterSetName = 'File')] + [Parameter(ParameterSetName = 'FileWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [ValidateRange(2, 2147483647)] [System.UInt32] - $MaximumFiles, + $MaximumFileSize, - # TODO:Maybe not use default it the parameter inte not necessary? - [Parameter(ParameterSetName = 'File')] + [Parameter(ParameterSetName = 'FileWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [ValidateSet('Megabyte', 'Gigabyte', 'Terabyte')] + [System.String] + $MaximumFileSizeUnit, + + [Parameter(ParameterSetName = 'FileWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithMaxFiles', Mandatory = $true)] [System.UInt32] - $MaximumFileSize = 10, + $MaximumFiles, - # TODO:Maybe not use default it the parameter inte not necessary? - [Parameter(ParameterSetName = 'File')] - [ValidateSet('KB', 'MB', 'GB')] - [System.String] - $MaximumFileSizeUnit = 'MB', + [Parameter(ParameterSetName = 'FileWithMaxFiles')] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxFiles')] + [System.Management.Automation.SwitchParameter] + $ReserveDiskSpace, - [Parameter(ParameterSetName = 'File')] + [Parameter(ParameterSetName = 'FileWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'FileWithMaxRolloverFiles', Mandatory = $true)] + [ValidateRange(0, 2147483647)] [System.UInt32] $MaximumRolloverFiles ) - switch ($PSCmdlet.ParameterSetName) + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + + $queryType = switch ($PSCmdlet.ParameterSetName) { 'Log' { # Translate the value for Type. - $queryType = ( + ( @{ - SecurityLog = 'SECURITY_LOG' + SecurityLog = 'SECURITY_LOG' ApplicationLog = 'APPLICATION_LOG' } ).$Type @@ -129,73 +181,186 @@ function New-SqlDscAudit 'File' { - $queryType = 'FILE' + 'FILE' } } $query = 'CREATE SERVER AUDIT [{0}] TO {1}' -f $Name, $queryType - # Translate the value for OnFailure. - $queryOnFailure = ( - @{ - Continue = 'CONTINUE' - FailOperation = 'FAIL_OPERATION' - ShutDown = 'SHUTDOWN' + if ($PSCmdlet.ParameterSetName -match 'File') + { + $query += (" (FILEPATH = '{0}'" -f $Path) + + if ($PSCmdlet.ParameterSetName -match 'FileWithSize') + { + $queryMaximumFileSizeUnit = ( + @{ + Megabyte = 'MB' + Gigabyte = 'GB' + Terabyte = 'TB' + } + ).$MaximumFileSizeUnit + + # MAXSIZE: cspell: disable-line cspell: disable-next-line + $query += (', MAXSIZE {0} {1}' -f $MaximumFileSize, $queryMaximumFileSizeUnit) + } + + <# + TODO: For Set-SqlDscAudit: Switching between MaximumFiles and MaximumRolloverFiles must + run alter() between. + + $sqlServerObject.Audits['File1'].MaximumRolloverFiles = 0 + $sqlServerObject.Audits['File1'].Alter() + $sqlServerObject.Audits['File1'].MaximumFiles = 1 + $sqlServerObject.Audits['File1'].Alter() + #> + if ($PSCmdlet.ParameterSetName -in @('FileWithMaxFiles', 'FileWithSizeAndMaxFiles')) + { + $query += (', MAX_FILES = {0}' -f $MaximumFiles) + + if ($PSBoundParameters.ContainsKey('ReserveDiskSpace')) + { + # Translate the value for ReserveDiskSpace. + $queryReservDiskSpace = ( + @{ + True = 'ON' + False = 'OFF' + } + ).($ReserveDiskSpace.IsPresent.ToString()) + + $query += (', RESERVE_DISK_SPACE = {0}' -f $queryReservDiskSpace) + } + } + + if ($PSCmdlet.ParameterSetName -in @('FileWithMaxRolloverFiles', 'FileWithSizeAndMaxRolloverFiles')) + { + $query += (', MAX_ROLLOVER_FILES = {0}' -f $MaximumFiles) + } + + $query += ')' + } + + + $needWithPart = ( + $PSBoundParameters.ContainsKey('OnFailure') -or + $PSBoundParameters.ContainsKey('QueueDelay') -or + $PSBoundParameters.ContainsKey('AuditGuid') -or + $PSBoundParameters.ContainsKey('OperatorAudit') + ) + + if ($needWithPart) + { + $query += ' WITH (' + + $hasWithPartOption = $false + + foreach ($option in @('OnFailure', 'QueueDelay', 'AuditGuid', 'OperatorAudit')) + { + if ($PSBoundParameters.ContainsKey($option)) + { + <# + If there was already an option added, and another need to be + added, split them with a comma. + #> + if ($hasWithPartOption) + { + $query += ', ' + } + + switch ($option) + { + 'QueueDelay' + { + $query += ('QUEUE_DELAY = {0}' -f $QueueDelay) + + $hasWithPartOption = $true + } + + 'OnFailure' + { + # Translate the value for OnFailure. + $queryOnFailure = ( + @{ + Continue = 'CONTINUE' + FailOperation = 'FAIL_OPERATION' + ShutDown = 'SHUTDOWN' + } + ).$OnFailure + + $query += ('ON_FAILURE = {0}' -f $queryOnFailure) + + $hasWithPartOption = $true + } + + 'AuditGuid' + { + $query += ("AUDIT_GUID = '{0}'" -f $AuditGuid) + + $hasWithPartOption = $true + } + + 'OperatorAudit' + { + # Translate the value for OperatorAudit. + $queryOperatorAudit = ( + @{ + True = 'ON' + False = 'OFF' + } + ).($OperatorAudit.IsPresent.ToString()) + + $query += ('OPERATOR_AUDIT = {0}' -f $queryOperatorAudit) + + $hasWithPartOption = $true + } + } + } } - ).$OnFailure - - # 'File' - # { - # $strReserveDiskSpace = 'OFF' - # if ($ReserveDiskSpace) - # { - # $strReserveDiskSpace = 'ON' - # } - - # $strFiles = '' - # if ($MaximumFiles) - # { - # $strFiles = 'MAX_FILES = {0},' -f $MaximumFiles - # } - # if ($MaximumRolloverFiles) - # { - # TODO: If not passed then UNLIMITED should be used (it is default when not adding MAX_ROLLOVER_FILES to the query). - # $strFiles = 'MAX_ROLLOVER_FILES = {0},' -f $MaximumRolloverFiles - # } - - # # TODO: Should handle Filter (WHERE-clause) - - # $target = 'FILE ( - # FILEPATH = N''{0}'', - # MAXSIZE = {1} {2}, - # {3} - # RESERVE_DISK_SPACE = {4} )' -f - # $FilePath, - # $MaximumFileSize, - # $MaximumFileSizeUnit, - # $strFiles, - # $strReserveDiskSpace - # } - - # $withPart = 'QUEUE_DELAY = {0}, ON_FAILURE = {1}' -f @( - # $QueueDelay, - # $queryOnFailure - # ) - - # $invokeQueryParameters = @{ - # ServerName = $ServerName - # InstanceName = $InstanceName - # Database = 'MASTER' - # } - - Invoke-Query @invokeQueryParameters -Query $query - - # 'CREATE SERVER AUDIT [{0}] TO {1} - # WITH ( - # {2} - # );' -f - # $Name, - # $Target, - # $WithPart - # ) + + $query += ')' + } + + # TODO: This cannot allow SQL Injection + if ($PSBoundParameters.ContainsKey('Filter')) + { + $query += ' WHERE (' + + # ::= + # { + # [NOT ] + # [ { AND | OR } [NOT ] { } ] + # [,...n ] + # } + + # ::= + # event_field_name { = | < > | ! = | > | > = | < | < = | LIKE } { number | ' string ' } + + # WHERE ([server_principal_name] like '%ADMINISTRATOR') + + # WHERE [Schema_Name] = 'sys' AND [Object_Name] = 'all_objects' + + # WHERE ( + # [Schema_Name] = 'sys' AND [Object_Name] = 'all_objects' + #) OR ( + # [Schema_Name] = 'sys' AND [Object_Name] = 'database_permissions' + #) + + $query += ')' + } + + $verboseDescriptionMessage = $script:localizedData.Audit_ChangePermissionShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName + $verboseWarningMessage = $script:localizedData.Audit_ChangePermissionShouldProcessVerboseWarning -f $Name + $captionMessage = $script:localizedData.Audit_ChangePermissionShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + $invokeSqlDscQueryParameters = @{ + ServerObject = $ServerObject + DatabaseName = 'master' + Query = $query + ErrorAction = 'Stop' + } + + Invoke-SqlDscQuery @invokeSqlDscQueryParameters + } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 734b3a377..0878bb7a8 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -26,7 +26,7 @@ ConvertFrom-StringData @' ## Get-SqlDscServerPermission, Set-SqlDscServerPermission ServerPermission_MissingPrincipal = The principal '{0}' is not a login on the instance '{1}'. - ## Set-SqlDscDatabasePermission + ## Set-SqlDscServerPermission ServerPermission_IgnoreWithGrantForStateDeny = The parameter WithGrant cannot be used together with the state Deny, the parameter WithGrant is ignored. ServerPermission_ChangePermissionShouldProcessVerboseDescription = Changing the permission for the principal '{0}' on the instance '{1}'. ServerPermission_ChangePermissionShouldProcessVerboseWarning = Are you sure you want you change the permission for the principal '{0}'? @@ -38,4 +38,11 @@ ConvertFrom-StringData @' ## Class DatabasePermission InvalidTypeForCompare = Invalid type in comparison. Expected type [{0}], but the type was [{1}]. (DP0001) + + ## New-SqlDscAudit + Audit_PathParameterValueInvalid = The path '{0}' does not exist. Audit file can only be created in a path that already exist and where the SQL Server instance has permission to write. + Audit_ChangePermissionShouldProcessVerboseDescription = Adding the audit '{0}' on the instance '{1}'. + Audit_ChangePermissionShouldProcessVerboseWarning = Are you sure you want you add the audit '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Audit_ChangePermissionShouldProcessCaption = Add audit on instance '@ diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 new file mode 100644 index 000000000..0584bb84f --- /dev/null +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -0,0 +1,385 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'New-SqlDscAudit' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + MockParameterSetName = 'Log' + MockExpectedParameters = '-ServerObject -Name -Type [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'File' + MockExpectedParameters = '-ServerObject -Name -Path [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'FileWithSize' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'FileWithMaxFiles' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'FileWithMaxRolloverFiles' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'FileWithSizeAndMaxFiles' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'FileWithSizeAndMaxRolloverFiles' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'New-SqlDscAudit').ParameterSets | + Where-Object -FilterScript { + $_.Name -eq $mockParameterSetName + } | + Select-Object -Property @( + @{ + Name = 'ParameterSetName' + Expression = { $_.Name } + }, + @{ + Name = 'ParameterListAsString' + Expression = { $_.ToString() } + } + ) + + $result.ParameterSetName | Should -Be $MockParameterSetName + $result.ParameterListAsString | Should -Be $MockExpectedParameters + } + + Context 'When adding an application log audit using mandatory parameters' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + Mock -CommandName Invoke-SqlDscQuery + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Type = 'ApplicationLog' + Name = 'Log1' + } + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -Confirm:$false @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq 'CREATE SERVER AUDIT [Log1] TO APPLICATION_LOG' + } + } + } + + Context 'When using parameter Force' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -Force @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq 'CREATE SERVER AUDIT [Log1] TO APPLICATION_LOG' + } + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -WhatIf @mockDefaultParameters + + Should -Not -Invoke -CommandName Invoke-SqlDscQuery + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mock with the expected query' { + $mockServerObject | New-SqlDscAudit -Type 'ApplicationLog' -Name 'Log1' + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq 'CREATE SERVER AUDIT [Log1] TO APPLICATION_LOG' + } + } + } + } + + Context 'When adding an security log audit using mandatory parameters' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + Mock -CommandName Invoke-SqlDscQuery + Mock -CommandName Test-Path -MockWith { + return $true + } + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Type = 'SecurityLog' + Name = 'Log1' + } + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -Confirm:$false @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq 'CREATE SERVER AUDIT [Log1] TO SECURITY_LOG' + } + } + } + + Context 'When using parameter Force' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -Force @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq 'CREATE SERVER AUDIT [Log1] TO SECURITY_LOG' + } + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -WhatIf @mockDefaultParameters + + Should -Not -Invoke -CommandName Invoke-SqlDscQuery + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mock with the expected query' { + $mockServerObject | New-SqlDscAudit -Type 'SecurityLog' -Name 'Log1' + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq 'CREATE SERVER AUDIT [Log1] TO SECURITY_LOG' + } + } + } + } + + Context 'When adding an file audit using mandatory parameters' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + Mock -CommandName Invoke-SqlDscQuery + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + } + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -Confirm:$false @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp')" + } + } + } + + Context 'When using parameter Force' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -Force @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp')" + } + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -WhatIf @mockDefaultParameters + + Should -Not -Invoke -CommandName Invoke-SqlDscQuery + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mock with the expected query' { + $mockServerObject | New-SqlDscAudit -Path 'C:\Temp' -Name 'Log1' + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp')" + } + } + } + } + + Context 'When adding an file audit and passing an invalid path' { + BeforeAll { + Mock -CommandName Test-Path -MockWith { + return $false + } + } + + It 'Should throw the correct error' { + $mockNewSqlDscAuditParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Path = 'C:\Temp' + Name = 'Log1' + } + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Audit_PathParameterValueInvalid + } + + $mockErrorMessage = "Cannot validate argument on parameter 'Path'. " + ($mockErrorMessage -f 'C:\Temp') + + + { New-SqlDscAudit @mockNewSqlDscAuditParameters } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + Context 'When passing file audit optional parameter AuditGuid' { + BeforeAll { + Mock -CommandName Invoke-SqlDscQuery + + $mockDefaultParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + It 'Should call the mock with the expected query' { + New-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (AUDIT_GUID = 'b5962b93-a359-42ef-bf1e-193e8a5f6222')" + } + } + + Context 'When passing an invalid GUID' { + It 'Should call the mock with the expected query' { + $mockErrorMessage = 'Cannot validate argument on parameter ''AuditGuid''. The argument "not a guid" does not match the "^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$" pattern.' + + # Escape bracket so that Should -Throw works. + $mockErrorMessage = $mockErrorMessage -replace '\[', '`[' + + { New-SqlDscAudit -AuditGuid 'not a guid' @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage + '*') + } + } + } + + Context 'When passing file audit optional parameter OperatorAudit' { + BeforeAll { + Mock -CommandName Invoke-SqlDscQuery + + $mockDefaultParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + It 'Should call the mock with the expected query' { + New-SqlDscAudit -OperatorAudit @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (OPERATOR_AUDIT = ON)" + } + } + + Context 'When passing $false for parameter OperatorAudit' { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -OperatorAudit:$false @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (OPERATOR_AUDIT = OFF)" + } + } + } + } + + Context 'When passing file audit optional parameter OnFailure' { + BeforeAll { + Mock -CommandName Invoke-SqlDscQuery + + $mockDefaultParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + Context 'When passing the value ' -ForEach @( + @{ + MockOnFailureValue = 'Continue' + MockExpectedQueryValue = 'CONTINUE' + } + @{ + MockOnFailureValue = 'FailOperation' + MockExpectedQueryValue = 'FAIL_OPERATION' + } + @{ + MockOnFailureValue = 'ShutDown' + MockExpectedQueryValue = 'SHUTDOWN' + } + ) { + It 'Should call the mock with the expected query' { + New-SqlDscAudit -OperatorAudit @mockDefaultParameters + + Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { + Write-Verbose -Verbose -Message $Query + $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (ON_FAILURE = $MockExpectedQueryValue)" + } + } + } + } +} From 3f1f46ac3ac8a34046b87c5e3a77ae10126e5b8f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 5 Aug 2022 17:51:44 +0200 Subject: [PATCH 16/71] Fix New-SqlDscAudit --- CHANGELOG.md | 1 + source/Public/New-SqlDscAudit.ps1 | 204 ++---- source/en-US/SqlServerDsc.strings.psd1 | 1 + tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 709 +++++++++++++++++--- tests/Unit/Stubs/SMO.cs | 72 ++ 5 files changed, 746 insertions(+), 241 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f520bdd1..1dce12d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Get-SqlDscServerPermission` - `Set-SqlDscServerPermission` - `Invoke-SqlDscQuery` + - `New-SqlDscAudit` - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - Add new resource SqlServerAudit. diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index 55428f41e..8814c7f61 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -25,6 +25,16 @@ Specifies the GUID found in the mirrored database. To support scenarios such as database mirroring an audit needs a specific GUID. + .PARAMETER Force + Specifies that the audit should be created with out any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s audits should be refreshed before creating + the audit object. This is helpful when audits could have been modified outside + of the **ServerObject**, for example through T-SQL. But on instances with + a large amount of audits it might be better to make sure the ServerObject + is recent enough. + .PARAMETER OperatorAudit Specifies if auditing will capture Microsoft support engineers operations during support requests. Applies to Azure SQL Managed Instance only. @@ -101,15 +111,14 @@ function New-SqlDscAudit [Parameter()] [System.Management.Automation.SwitchParameter] - $OperatorAudit, + $Force, [Parameter()] [System.Management.Automation.SwitchParameter] - $Force, + $Refresh, [Parameter(ParameterSetName = 'Log', Mandatory = $true)] [ValidateSet('SecurityLog', 'ApplicationLog')] - #[ValidateSet('File', 'SecurityLog', 'ApplicationLog')] [System.String] $Type, @@ -166,30 +175,50 @@ function New-SqlDscAudit $ConfirmPreference = 'None' } + if ($Refresh.IsPresent) + { + # Make sure the audits are up-to-date to get any newly created audits. + $ServerObject.Audits.Refresh() + } + + if ($ServerObject.Audits[$Name]) + { + $missingDatabaseMessage = $script:localizedData.Audit_AlreadyPresent -f $Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingDatabaseMessage, + 'NSDA0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } + + # TODO: This is for Set-SqlDscAudit + # $auditObject = $ServerObject.Audits[$Name] + # $auditObject.Refresh() + + $auditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($ServerObject, $Name) + $queryType = switch ($PSCmdlet.ParameterSetName) { 'Log' { - # Translate the value for Type. - ( - @{ - SecurityLog = 'SECURITY_LOG' - ApplicationLog = 'APPLICATION_LOG' - } - ).$Type + $Type } - 'File' + default { - 'FILE' + 'File' } } - $query = 'CREATE SERVER AUDIT [{0}] TO {1}' -f $Name, $queryType + $auditObject.DestinationType = $queryType if ($PSCmdlet.ParameterSetName -match 'File') { - $query += (" (FILEPATH = '{0}'" -f $Path) + $auditObject.FilePath = $Path if ($PSCmdlet.ParameterSetName -match 'FileWithSize') { @@ -201,151 +230,53 @@ function New-SqlDscAudit } ).$MaximumFileSizeUnit - # MAXSIZE: cspell: disable-line cspell: disable-next-line - $query += (', MAXSIZE {0} {1}' -f $MaximumFileSize, $queryMaximumFileSizeUnit) + $auditObject.MaximumFileSize = $MaximumFileSize + $auditObject.MaximumFileSizeUnit = $queryMaximumFileSizeUnit } <# TODO: For Set-SqlDscAudit: Switching between MaximumFiles and MaximumRolloverFiles must run alter() between. - $sqlServerObject.Audits['File1'].MaximumRolloverFiles = 0 - $sqlServerObject.Audits['File1'].Alter() - $sqlServerObject.Audits['File1'].MaximumFiles = 1 - $sqlServerObject.Audits['File1'].Alter() + $ServerObject.Audits['File1'].MaximumRolloverFiles = 0 + $ServerObject.Audits['File1'].Alter() + $ServerObject.Audits['File1'].MaximumFiles = 1 + $ServerObject.Audits['File1'].Alter() #> if ($PSCmdlet.ParameterSetName -in @('FileWithMaxFiles', 'FileWithSizeAndMaxFiles')) { - $query += (', MAX_FILES = {0}' -f $MaximumFiles) + $auditObject.MaximumFiles = $MaximumFiles if ($PSBoundParameters.ContainsKey('ReserveDiskSpace')) { - # Translate the value for ReserveDiskSpace. - $queryReservDiskSpace = ( - @{ - True = 'ON' - False = 'OFF' - } - ).($ReserveDiskSpace.IsPresent.ToString()) - - $query += (', RESERVE_DISK_SPACE = {0}' -f $queryReservDiskSpace) + $auditObject.ReserveDiskSpace = $ReserveDiskSpace.IsPresent } } if ($PSCmdlet.ParameterSetName -in @('FileWithMaxRolloverFiles', 'FileWithSizeAndMaxRolloverFiles')) { - $query += (', MAX_ROLLOVER_FILES = {0}' -f $MaximumFiles) + $auditObject.MaximumRolloverFiles = $MaximumRolloverFiles } - - $query += ')' } - - $needWithPart = ( - $PSBoundParameters.ContainsKey('OnFailure') -or - $PSBoundParameters.ContainsKey('QueueDelay') -or - $PSBoundParameters.ContainsKey('AuditGuid') -or - $PSBoundParameters.ContainsKey('OperatorAudit') - ) - - if ($needWithPart) + if ($PSBoundParameters.ContainsKey('OnFailure')) { - $query += ' WITH (' - - $hasWithPartOption = $false - - foreach ($option in @('OnFailure', 'QueueDelay', 'AuditGuid', 'OperatorAudit')) - { - if ($PSBoundParameters.ContainsKey($option)) - { - <# - If there was already an option added, and another need to be - added, split them with a comma. - #> - if ($hasWithPartOption) - { - $query += ', ' - } + $auditObject.OnFailure = $OnFailure + } - switch ($option) - { - 'QueueDelay' - { - $query += ('QUEUE_DELAY = {0}' -f $QueueDelay) - - $hasWithPartOption = $true - } - - 'OnFailure' - { - # Translate the value for OnFailure. - $queryOnFailure = ( - @{ - Continue = 'CONTINUE' - FailOperation = 'FAIL_OPERATION' - ShutDown = 'SHUTDOWN' - } - ).$OnFailure - - $query += ('ON_FAILURE = {0}' -f $queryOnFailure) - - $hasWithPartOption = $true - } - - 'AuditGuid' - { - $query += ("AUDIT_GUID = '{0}'" -f $AuditGuid) - - $hasWithPartOption = $true - } - - 'OperatorAudit' - { - # Translate the value for OperatorAudit. - $queryOperatorAudit = ( - @{ - True = 'ON' - False = 'OFF' - } - ).($OperatorAudit.IsPresent.ToString()) - - $query += ('OPERATOR_AUDIT = {0}' -f $queryOperatorAudit) - - $hasWithPartOption = $true - } - } - } - } + if ($PSBoundParameters.ContainsKey('QueueDelay')) + { + $auditObject.QueueDelay = $QueueDelay + } - $query += ')' + if ($PSBoundParameters.ContainsKey('AuditGuid')) + { + $auditObject.Guid = $AuditGuid } - # TODO: This cannot allow SQL Injection if ($PSBoundParameters.ContainsKey('Filter')) { - $query += ' WHERE (' - - # ::= - # { - # [NOT ] - # [ { AND | OR } [NOT ] { } ] - # [,...n ] - # } - - # ::= - # event_field_name { = | < > | ! = | > | > = | < | < = | LIKE } { number | ' string ' } - - # WHERE ([server_principal_name] like '%ADMINISTRATOR') - - # WHERE [Schema_Name] = 'sys' AND [Object_Name] = 'all_objects' - - # WHERE ( - # [Schema_Name] = 'sys' AND [Object_Name] = 'all_objects' - #) OR ( - # [Schema_Name] = 'sys' AND [Object_Name] = 'database_permissions' - #) - - $query += ')' + $auditObject.Filter = $Filter } $verboseDescriptionMessage = $script:localizedData.Audit_ChangePermissionShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName @@ -354,13 +285,6 @@ function New-SqlDscAudit if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) { - $invokeSqlDscQueryParameters = @{ - ServerObject = $ServerObject - DatabaseName = 'master' - Query = $query - ErrorAction = 'Stop' - } - - Invoke-SqlDscQuery @invokeSqlDscQueryParameters + $auditObject.Create() } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 0878bb7a8..e5cbb974b 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -45,4 +45,5 @@ ConvertFrom-StringData @' Audit_ChangePermissionShouldProcessVerboseWarning = Are you sure you want you add the audit '{0}'? # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Audit_ChangePermissionShouldProcessCaption = Add audit on instance + Audit_AlreadyPresent = There is already an audit with the name '{0}'. '@ diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 index 0584bb84f..26c12e67e 100644 --- a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -49,31 +49,31 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { It 'Should have the correct parameters in parameter set ' -ForEach @( @{ MockParameterSetName = 'Log' - MockExpectedParameters = '-ServerObject -Name -Type [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Type [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'File' - MockExpectedParameters = '-ServerObject -Name -Path [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSize' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithMaxFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSizeAndMaxFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSizeAndMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-OperatorAudit] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscAudit').ParameterSets | @@ -97,10 +97,32 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When adding an application log audit using mandatory parameters' { BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - $mockServerObject.InstanceName = 'TestInstance' + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } - Mock -CommandName Invoke-SqlDscQuery + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ ServerObject = $mockServerObject @@ -109,55 +131,88 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { } } + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + Context 'When using parameter Confirm with value $false' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -Confirm:$false @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq 'CREATE SERVER AUDIT [Log1] TO APPLICATION_LOG' - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'ApplicationLog' + + $mockMethodCreateCallCount | Should -Be 1 } } Context 'When using parameter Force' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -Force @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq 'CREATE SERVER AUDIT [Log1] TO APPLICATION_LOG' - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'ApplicationLog' + + $mockMethodCreateCallCount | Should -Be 1 } } Context 'When using parameter WhatIf' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -WhatIf @mockDefaultParameters - Should -Not -Invoke -CommandName Invoke-SqlDscQuery + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'ApplicationLog' + + $mockMethodCreateCallCount | Should -Be 0 } } Context 'When passing parameter ServerObject over the pipeline' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { $mockServerObject | New-SqlDscAudit -Type 'ApplicationLog' -Name 'Log1' - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq 'CREATE SERVER AUDIT [Log1] TO APPLICATION_LOG' - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'ApplicationLog' + + $mockMethodCreateCallCount | Should -Be 1 } } } Context 'When adding an security log audit using mandatory parameters' { BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - $mockServerObject.InstanceName = 'TestInstance' - - Mock -CommandName Invoke-SqlDscQuery - Mock -CommandName Test-Path -MockWith { - return $true + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject } + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' + $mockDefaultParameters = @{ ServerObject = $mockServerObject Type = 'SecurityLog' @@ -165,51 +220,87 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { } } + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + Context 'When using parameter Confirm with value $false' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -Confirm:$false @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq 'CREATE SERVER AUDIT [Log1] TO SECURITY_LOG' - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'SecurityLog' + + $mockMethodCreateCallCount | Should -Be 1 } } Context 'When using parameter Force' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -Force @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq 'CREATE SERVER AUDIT [Log1] TO SECURITY_LOG' - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'SecurityLog' + + $mockMethodCreateCallCount | Should -Be 1 } } Context 'When using parameter WhatIf' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -WhatIf @mockDefaultParameters - Should -Not -Invoke -CommandName Invoke-SqlDscQuery + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'SecurityLog' + + $mockMethodCreateCallCount | Should -Be 0 } } Context 'When passing parameter ServerObject over the pipeline' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { $mockServerObject | New-SqlDscAudit -Type 'SecurityLog' -Name 'Log1' - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq 'CREATE SERVER AUDIT [Log1] TO SECURITY_LOG' - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'SecurityLog' + + $mockMethodCreateCallCount | Should -Be 1 } } } Context 'When adding an file audit using mandatory parameters' { BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - $mockServerObject.InstanceName = 'TestInstance' + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } - Mock -CommandName Invoke-SqlDscQuery + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ ServerObject = $mockServerObject @@ -218,41 +309,59 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { } } + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + Context 'When using parameter Confirm with value $false' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -Confirm:$false @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp')" - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + + $mockMethodCreateCallCount | Should -Be 1 } } Context 'When using parameter Force' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -Force @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp')" - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + + $mockMethodCreateCallCount | Should -Be 1 } } Context 'When using parameter WhatIf' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { New-SqlDscAudit -WhatIf @mockDefaultParameters - Should -Not -Invoke -CommandName Invoke-SqlDscQuery + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + + $mockMethodCreateCallCount | Should -Be 0 } } Context 'When passing parameter ServerObject over the pipeline' { - It 'Should call the mock with the expected query' { + It 'Should call the mocked method and have correct values in the object' { $mockServerObject | New-SqlDscAudit -Path 'C:\Temp' -Name 'Log1' - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp')" - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + + $mockMethodCreateCallCount | Should -Be 1 } } } @@ -277,109 +386,507 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockErrorMessage = "Cannot validate argument on parameter 'Path'. " + ($mockErrorMessage -f 'C:\Temp') - { New-SqlDscAudit @mockNewSqlDscAuditParameters } | Should -Throw -ExpectedMessage $mockErrorMessage } } - Context 'When passing file audit optional parameter AuditGuid' { + Context 'When passing file audit optional parameters MaximumFileSize and MaximumFileSizeUnit' { BeforeAll { - Mock -CommandName Invoke-SqlDscQuery + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ - ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + ServerObject = $mockServerObject Name = 'Log1' Path = 'C:\Temp' Force = $true } } - It 'Should call the mock with the expected query' { - New-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' @mockDefaultParameters + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -MaximumFileSize 1000 -MaximumFileSizeUnit 'Megabyte' @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.MaximumFileSize | Should -Be 1000 + $mockCreateAuditObject.MaximumFileSizeUnit | Should -Be 'Mb' - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (AUDIT_GUID = 'b5962b93-a359-42ef-bf1e-193e8a5f6222')" + $mockMethodCreateCallCount | Should -Be 1 + } + } + + Context 'When passing file audit optional parameters MaximumFiles' { + BeforeAll { + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + Force = $true } } - Context 'When passing an invalid GUID' { - It 'Should call the mock with the expected query' { - $mockErrorMessage = 'Cannot validate argument on parameter ''AuditGuid''. The argument "not a guid" does not match the "^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$" pattern.' + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } - # Escape bracket so that Should -Throw works. - $mockErrorMessage = $mockErrorMessage -replace '\[', '`[' + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -MaximumFiles 2 @mockDefaultParameters - { New-SqlDscAudit -AuditGuid 'not a guid' @mockDefaultParameters } | - Should -Throw -ExpectedMessage ($mockErrorMessage + '*') + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.MaximumFiles | Should -Be 2 + + $mockMethodCreateCallCount | Should -Be 1 + } + } + + Context 'When passing file audit optional parameters MaximumFiles and ReserveDiskSpace' { + BeforeAll { + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -MaximumFiles 2 -ReserveDiskSpace @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.MaximumFiles | Should -Be 2 + $mockCreateAuditObject.ReserveDiskSpace | Should -BeTrue + + $mockMethodCreateCallCount | Should -Be 1 + } + + Context 'When ReserveDiskSpace is set to $false' { + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -MaximumFiles 2 -ReserveDiskSpace:$false @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.MaximumFiles | Should -Be 2 + $mockCreateAuditObject.ReserveDiskSpace | Should -BeFalse + + $mockMethodCreateCallCount | Should -Be 1 } } } - Context 'When passing file audit optional parameter OperatorAudit' { + Context 'When passing file audit optional parameters MaximumRolloverFiles' { BeforeAll { - Mock -CommandName Invoke-SqlDscQuery + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ - ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + ServerObject = $mockServerObject Name = 'Log1' Path = 'C:\Temp' Force = $true } } - It 'Should call the mock with the expected query' { - New-SqlDscAudit -OperatorAudit @mockDefaultParameters + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -MaximumRolloverFiles 2 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.MaximumRolloverFiles | Should -Be 2 + + $mockMethodCreateCallCount | Should -Be 1 + } + } + + Context 'When passing audit optional parameter AuditGuid' { + BeforeAll { + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (OPERATOR_AUDIT = ON)" + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + Force = $true } } - Context 'When passing $false for parameter OperatorAudit' { - It 'Should call the mock with the expected query' { - New-SqlDscAudit -OperatorAudit:$false @mockDefaultParameters + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (OPERATOR_AUDIT = OFF)" - } + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.Guid | Should -Be 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + + $mockMethodCreateCallCount | Should -Be 1 + } + + Context 'When passing an invalid GUID' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Cannot validate argument on parameter ''AuditGuid''. The argument "not a guid" does not match the "^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$" pattern.' + + # Escape bracket so that Should -Throw works. + $mockErrorMessage = $mockErrorMessage -replace '\[', '`[' + + { New-SqlDscAudit -AuditGuid 'not a guid' @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage + '*') } } } - Context 'When passing file audit optional parameter OnFailure' { + Context 'When passing audit optional parameter OnFailure' { BeforeAll { - Mock -CommandName Invoke-SqlDscQuery + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ - ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + ServerObject = $mockServerObject Name = 'Log1' Path = 'C:\Temp' Force = $true } } + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + Context 'When passing the value ' -ForEach @( @{ MockOnFailureValue = 'Continue' - MockExpectedQueryValue = 'CONTINUE' } @{ MockOnFailureValue = 'FailOperation' - MockExpectedQueryValue = 'FAIL_OPERATION' } @{ MockOnFailureValue = 'ShutDown' - MockExpectedQueryValue = 'SHUTDOWN' } ) { - It 'Should call the mock with the expected query' { - New-SqlDscAudit -OperatorAudit @mockDefaultParameters + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -OnFailure $MockOnFailureValue @mockDefaultParameters - Should -Invoke -CommandName Invoke-SqlDscQuery -ParameterFilter { - Write-Verbose -Verbose -Message $Query - $Query -eq "CREATE SERVER AUDIT [Log1] TO FILE (FILEPATH = 'C:\Temp') WITH (ON_FAILURE = $MockExpectedQueryValue)" - } + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.OnFailure | Should -Be $MockOnFailureValue + + $mockMethodCreateCallCount | Should -Be 1 } } } + + Context 'When passing audit optional parameter QueueDelay' { + BeforeAll { + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodCreateCallCount | Should -Be 1 + } + } + + Context 'When passing audit optional parameter Filter' { + BeforeAll { + $script:mockCreateAuditObject = $null + + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $script:mockCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + New-SqlDscAudit -Filter "([server_principal_name] like '%ADMINISTRATOR'" @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockCreateAuditObject.Name | Should -Be 'Log1' + $mockCreateAuditObject.DestinationType | Should -Be 'File' + $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.Filter | Should -Be "([server_principal_name] like '%ADMINISTRATOR'" + + $mockMethodCreateCallCount | Should -Be 1 + } + } + + Context 'When the audit already exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{ + 'Log1' = New-Object -TypeName Object + } + } -PassThru -Force + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Type = 'ApplicationLog' + Force = $true + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Audit_AlreadyPresent + } + + { New-SqlDscAudit @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'Log1') + } + } } diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index a987f4ff2..f5ff36fbd 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -123,6 +123,38 @@ public enum EndpointEncryptionAlgorithm RC4Aes } + public enum AuditDestinationType : int + { + File = 0, + SecurityLog = 1, + ApplicationLog = 2, + Url = 3, + Unknown = 100, + } + + public enum AuditFileSizeUnit : int + { + Mb = 0, + Gb = 1, + Tb = 2, + } + + public enum OnFailureAction : int + { + Continue = 0, + Shutdown = 1, + FailOperation = 2, + } + + public enum SqlSmoState : int + { + Pending = 0, + Creating = 1, + Existing = 2, + ToBeDropped = 3, + Dropped = 4, + } + #endregion Public Enums #region Public Classes @@ -856,6 +888,46 @@ public Endpoint this[string name] } } + public class Audit + { + // Constructor + public Audit() { } + public Audit(Microsoft.SqlServer.Management.Smo.Server server, System.String name) { + this.Parent = server; + this.Name = name; + } + + // Property + public Microsoft.SqlServer.Management.Smo.Server Parent { get; set; } = null; + public System.DateTime? CreateDate { get; set; } = null; + public System.DateTime? DateLastModified { get; set; } = null; + public Microsoft.SqlServer.Management.Smo.AuditDestinationType? DestinationType { get; set; } = null; + public System.Boolean? Enabled { get; set; } = null; + public System.String FileName { get; set; } = null; + public System.String FilePath { get; set; } = null; + public System.String Filter { get; set; } = null; + public System.Guid? Guid { get; set; } = null; + public System.Int32? ID { get; set; } = null; + public System.Int32? MaximumFiles { get; set; } = null; + public System.Int32? MaximumFileSize { get; set; } = null; + public Microsoft.SqlServer.Management.Smo.AuditFileSizeUnit? MaximumFileSizeUnit { get; set; } = null; + public System.Int64? MaximumRolloverFiles { get; set; } = null; + public Microsoft.SqlServer.Management.Smo.OnFailureAction? OnFailure { get; set; } = null; + public System.Int32? QueueDelay { get; set; } = null; + public System.Boolean? ReserveDiskSpace { get; set; } = null; + public System.Int32? RetentionDays { get; set; } = null; + public System.String Name { get; set; } = null; + // public Microsoft.SqlServer.Management.Smo.AbstractCollectionBase ParentCollection { get; set; } + // public Microsoft.SqlServer.Management.Sdk.Sfc.Urn Urn { get; set; } + // public Microsoft.SqlServer.Management.Smo.SqlPropertyCollection Properties { get; set; } + // public Microsoft.SqlServer.Management.Common.ServerVersion ServerVersion { get; set; } + // public Microsoft.SqlServer.Management.Common.DatabaseEngineType DatabaseEngineType { get; set; } + // public Microsoft.SqlServer.Management.Common.DatabaseEngineEdition DatabaseEngineEdition { get; set; } + // public Microsoft.SqlServer.Management.Smo.ExecutionManager ExecutionManager { get; set; } + public System.Object UserData { get; set; } = null; + public Microsoft.SqlServer.Management.Smo.SqlSmoState? State { get; set; } = null; + } + #endregion Public Classes } From a7a75365eda2d5f82f3dc3f1087a9a28d2a50fce Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 5 Aug 2022 18:02:49 +0200 Subject: [PATCH 17/71] Fix AuditFilter --- source/Public/New-SqlDscAudit.ps1 | 6 +++--- tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index 8814c7f61..d6501748d 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -92,7 +92,7 @@ function New-SqlDscAudit [Parameter()] [System.String] - $Filter, + $AuditFilter, [Parameter()] [ValidateSet('Continue', 'FailOperation', 'Shutdown')] @@ -274,9 +274,9 @@ function New-SqlDscAudit $auditObject.Guid = $AuditGuid } - if ($PSBoundParameters.ContainsKey('Filter')) + if ($PSBoundParameters.ContainsKey('AuditFilter')) { - $auditObject.Filter = $Filter + $auditObject.Filter = $AuditFilter } $verboseDescriptionMessage = $script:localizedData.Audit_ChangePermissionShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 index 26c12e67e..863e6e64c 100644 --- a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -49,31 +49,31 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { It 'Should have the correct parameters in parameter set ' -ForEach @( @{ MockParameterSetName = 'Log' - MockExpectedParameters = '-ServerObject -Name -Type [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Type [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'File' - MockExpectedParameters = '-ServerObject -Name -Path [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSize' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithMaxFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSizeAndMaxFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSizeAndMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-Filter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscAudit').ParameterSets | @@ -851,7 +851,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { } It 'Should call the mocked method and have correct values in the object' { - New-SqlDscAudit -Filter "([server_principal_name] like '%ADMINISTRATOR'" @mockDefaultParameters + New-SqlDscAudit -AuditFilter "([server_principal_name] like '%ADMINISTRATOR'" @mockDefaultParameters # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' From b6f600be1d5519b513499e4015daa7c41eedc846 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 6 Aug 2022 14:28:52 +0200 Subject: [PATCH 18/71] Fix New-SqlDscAudit --- source/Public/New-SqlDscAudit.ps1 | 46 ++++++++++---- source/en-US/SqlServerDsc.strings.psd1 | 6 +- tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 66 +++++++++++++++++---- 3 files changed, 94 insertions(+), 24 deletions(-) diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index d6501748d..a5f39cc1d 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -29,11 +29,11 @@ Specifies that the audit should be created with out any confirmation. .PARAMETER Refresh - Specifies that the **ServerObject**'s audits should be refreshed before creating - the audit object. This is helpful when audits could have been modified outside - of the **ServerObject**, for example through T-SQL. But on instances with - a large amount of audits it might be better to make sure the ServerObject - is recent enough. + Specifies that the **ServerObject**'s audits should be refreshed before + creating the audit object. This is helpful when audits could have been + modified outside of the **ServerObject**, for example through T-SQL. But + on instances with a large amount of audits it might be better to make + sure the ServerObject is recent enough. .PARAMETER OperatorAudit Specifies if auditing will capture Microsoft support engineers operations @@ -64,15 +64,20 @@ the files. If not specified then it is set to unlimited. .OUTPUTS - None. + `[Microsoft.SqlServer.Management.Smo.Audit]` is passing parameter **PassThru**, + otherwise none. .NOTES - See the SQL Server documentation: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql + This command has the confirm impact level set to medium since an audit is + created but by default is is not enabled. + + See the SQL Server documentation for more information for the possible + parameter values to pass to this command: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql #> function New-SqlDscAudit { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] - [OutputType()] + [OutputType([Microsoft.SqlServer.Management.Smo.Audit])] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param ( @@ -117,6 +122,10 @@ function New-SqlDscAudit [System.Management.Automation.SwitchParameter] $Refresh, + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + [Parameter(ParameterSetName = 'Log', Mandatory = $true)] [ValidateSet('SecurityLog', 'ApplicationLog')] [System.String] @@ -170,6 +179,16 @@ function New-SqlDscAudit $MaximumRolloverFiles ) + <# + TODO: Skapa Enable-SqlDscAudit eller Disable-SqlDscAudit + + TODO: Skapa Set-SqlDscAudit + + TODO: Skapa Remove-SqlDscAudit + + TODO: Skapa Get-SqlDscAudit som ska returnera alla Audit's om man inte + anger ett Filter-scriptblock + #> if ($Force.IsPresent) { $ConfirmPreference = 'None' @@ -279,12 +298,17 @@ function New-SqlDscAudit $auditObject.Filter = $AuditFilter } - $verboseDescriptionMessage = $script:localizedData.Audit_ChangePermissionShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName - $verboseWarningMessage = $script:localizedData.Audit_ChangePermissionShouldProcessVerboseWarning -f $Name - $captionMessage = $script:localizedData.Audit_ChangePermissionShouldProcessCaption + $verboseDescriptionMessage = $script:localizedData.Audit_Add_ShouldProcessVerboseDescription -f $Name, $ServerObject.InstanceName + $verboseWarningMessage = $script:localizedData.Audit_Add_ShouldProcessVerboseWarning -f $Name + $captionMessage = $script:localizedData.Audit_Add_ShouldProcessCaption if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) { $auditObject.Create() } + + if ($PassThru.IsPresent) + { + return $auditObject + } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index e5cbb974b..ac1b0bb9b 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -41,9 +41,9 @@ ConvertFrom-StringData @' ## New-SqlDscAudit Audit_PathParameterValueInvalid = The path '{0}' does not exist. Audit file can only be created in a path that already exist and where the SQL Server instance has permission to write. - Audit_ChangePermissionShouldProcessVerboseDescription = Adding the audit '{0}' on the instance '{1}'. - Audit_ChangePermissionShouldProcessVerboseWarning = Are you sure you want you add the audit '{0}'? + Audit_Add_ShouldProcessVerboseDescription = Adding the audit '{0}' on the instance '{1}'. + Audit_Add_ShouldProcessVerboseWarning = Are you sure you want you add the audit '{0}'? # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. - Audit_ChangePermissionShouldProcessCaption = Add audit on instance + Audit_Add_ShouldProcessCaption = Add audit on instance Audit_AlreadyPresent = There is already an audit with the name '{0}'. '@ diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 index 863e6e64c..dda96feab 100644 --- a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -49,31 +49,31 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { It 'Should have the correct parameters in parameter set ' -ForEach @( @{ MockParameterSetName = 'Log' - MockExpectedParameters = '-ServerObject -Name -Type [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Type [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'File' - MockExpectedParameters = '-ServerObject -Name -Path [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSize' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithMaxFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSizeAndMaxFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'FileWithSizeAndMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -Path -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'New-SqlDscAudit').ParameterSets | @@ -173,7 +173,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When passing parameter ServerObject over the pipeline' { It 'Should call the mocked method and have correct values in the object' { - $mockServerObject | New-SqlDscAudit -Type 'ApplicationLog' -Name 'Log1' + $mockServerObject | New-SqlDscAudit -Type 'ApplicationLog' -Name 'Log1' -Force # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' @@ -262,7 +262,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When passing parameter ServerObject over the pipeline' { It 'Should call the mocked method and have correct values in the object' { - $mockServerObject | New-SqlDscAudit -Type 'SecurityLog' -Name 'Log1' + $mockServerObject | New-SqlDscAudit -Type 'SecurityLog' -Name 'Log1' -Force # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' @@ -354,7 +354,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When passing parameter ServerObject over the pipeline' { It 'Should call the mocked method and have correct values in the object' { - $mockServerObject | New-SqlDscAudit -Path 'C:\Temp' -Name 'Log1' + $mockServerObject | New-SqlDscAudit -Path 'C:\Temp' -Name 'Log1' -Force # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' @@ -863,6 +863,52 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { } } + Context 'When passing optional parameter PassThru' { + BeforeAll { + Mock -CommandName New-Object -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Audit' + } -MockWith { + $mockNewCreateAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $PesterBoundParameters.ArgumentList[0], + $PesterBoundParameters.ArgumentList[1] + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Create' -Value { + $script:mockMethodCreateCallCount += 1 + } -PassThru -Force + + return $mockNewCreateAuditObject + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockServerObject.InstanceName = 'TestInstance' + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Path = 'C:\Temp' + Force = $true + } + } + + BeforeEach { + $script:mockMethodCreateCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + $newSqlDscAuditResult = New-SqlDscAudit -PassThru @mockDefaultParameters + + $newSqlDscAuditResult.Name | Should -Be 'Log1' + $newSqlDscAuditResult.DestinationType | Should -Be 'File' + $newSqlDscAuditResult.FilePath | Should -Be 'C:\Temp' + + $mockMethodCreateCallCount | Should -Be 1 + } + } + Context 'When the audit already exist' { BeforeAll { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | From 3fdc3590ad16bf570c52de4df8fb71f75e98ad61 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 6 Aug 2022 14:29:09 +0200 Subject: [PATCH 19/71] Fix Remove-SqlDscAudit --- CHANGELOG.md | 1 + source/Public/Remove-SqlDscAudit.ps1 | 97 ++++++++ source/en-US/SqlServerDsc.strings.psd1 | 7 + .../Unit/Public/Remove-SqlDscAudit.Tests.ps1 | 217 ++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 source/Public/Remove-SqlDscAudit.ps1 create mode 100644 tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dce12d2f..10aef9401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Set-SqlDscServerPermission` - `Invoke-SqlDscQuery` - `New-SqlDscAudit` + - `Remove-SqlDscAudit` - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - Add new resource SqlServerAudit. diff --git a/source/Public/Remove-SqlDscAudit.ps1 b/source/Public/Remove-SqlDscAudit.ps1 new file mode 100644 index 000000000..2899d833d --- /dev/null +++ b/source/Public/Remove-SqlDscAudit.ps1 @@ -0,0 +1,97 @@ +<# + .SYNOPSIS + Removes a server audit. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER AuditObject + Specifies a audit object to remove. + + .PARAMETER Name + Specifies the name of the server audit to be removed. + + .PARAMETER Force + Specifies that the audit should be removed with out any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s audits should be refreshed before + trying removing the audit object. This is helpful when audits could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of audits it might be better to make + sure the **ServerObject** is recent enough, or pass in **AuditObject**. + + .OUTPUTS + None. +#> +function Remove-SqlDscAudit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'AuditObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Audit] + $AuditObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + if ($Refresh.IsPresent) + { + # Make sure the audits are up-to-date to get any newly created audits. + $ServerObject.Audits.Refresh() + } + + $AuditObject = $ServerObject.Audits[$Name] + + if (-not $AuditObject) + { + $missingDatabaseMessage = $script:localizedData.Audit_Missing -f $Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingDatabaseMessage, + 'NSDA0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } + } + + $verboseDescriptionMessage = $script:localizedData.Audit_Remove_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Audit_Remove_ShouldProcessVerboseWarning -f $AuditObject.Name + $captionMessage = $script:localizedData.Audit_Remove_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + <# + If the passed audit object has already been dropped, then we silently + do nothing, using the method DropIfExist(), since the job is done. + #> + $AuditObject.DropIfExist() + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index ac1b0bb9b..8712201b0 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -46,4 +46,11 @@ ConvertFrom-StringData @' # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Audit_Add_ShouldProcessCaption = Add audit on instance Audit_AlreadyPresent = There is already an audit with the name '{0}'. + + ## New-SqlDscAudit + Audit_Remove_ShouldProcessVerboseDescription = Removing the audit '{0}' on the instance '{1}'. + Audit_Remove_ShouldProcessVerboseWarning = Are you sure you want you remove the audit '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Audit_Remove_ShouldProcessCaption = Remove audit on instance + Audit_Missing = There is no audit with the name '{0}'. '@ diff --git a/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 new file mode 100644 index 000000000..d38ec92e5 --- /dev/null +++ b/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 @@ -0,0 +1,217 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Remove-SqlDscAudit' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + MockParameterSetName = 'ServerObject' + MockExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObject' + MockExpectedParameters = '-AuditObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Remove-SqlDscAudit').ParameterSets | + Where-Object -FilterScript { + $_.Name -eq $mockParameterSetName + } | + Select-Object -Property @( + @{ + Name = 'ParameterSetName' + Expression = { $_.Name } + }, + @{ + Name = 'ParameterListAsString' + Expression = { $_.ToString() } + } + ) + + $result.ParameterSetName | Should -Be $MockParameterSetName + $result.ParameterListAsString | Should -Be $MockExpectedParameters + } + + Context 'When removing an audit by ServerObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockServerObject = $mockServerObject | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{ + 'Log1' = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExist' -Value { + $script:mockMethodDropIfExistCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + } + } + + BeforeEach { + $script:mockMethodDropIfExistCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Remove-SqlDscAudit -Confirm:$false @mockDefaultParameters + + $mockMethodDropIfExistCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Remove-SqlDscAudit -Force @mockDefaultParameters + + $mockMethodDropIfExistCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Remove-SqlDscAudit -WhatIf @mockDefaultParameters + + $mockMethodDropIfExistCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockServerObject | Remove-SqlDscAudit -Name 'Log1' -Force + + $mockMethodDropIfExistCallCount | Should -Be 1 + } + } + } + + Context 'When removing an audit by AuditObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExist' -Value { + $script:mockMethodDropIfExistCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + } + } + + BeforeEach { + $script:mockMethodDropIfExistCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Remove-SqlDscAudit -Confirm:$false @mockDefaultParameters + + $mockMethodDropIfExistCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Remove-SqlDscAudit -Force @mockDefaultParameters + + $mockMethodDropIfExistCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Remove-SqlDscAudit -WhatIf @mockDefaultParameters + + $mockMethodDropIfExistCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockAuditObject | Remove-SqlDscAudit -Force + + $mockMethodDropIfExistCallCount | Should -Be 1 + } + } + } + + Context 'When the audit does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + Force = $true + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Audit_Missing + } + + { Remove-SqlDscAudit @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'Log1') + } + } +} From a07ebd17cef49f1266851e0e73e35c40dab61922 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 6 Aug 2022 18:38:43 +0200 Subject: [PATCH 20/71] Fix Enable and Disable --- CHANGELOG.md | 2 + source/Public/Disable-SqlDscAudit.ps1 | 94 +++++++++++++++++++++ source/Public/Enable-SqlDscAudit.ps1 | 94 +++++++++++++++++++++ source/Public/New-SqlDscAudit.ps1 | 2 + source/Public/Remove-SqlDscAudit.ps1 | 3 +- source/en-US/SqlServerDsc.strings.psd1 | 18 +++- tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 54 ++++++------ 7 files changed, 237 insertions(+), 30 deletions(-) create mode 100644 source/Public/Disable-SqlDscAudit.ps1 create mode 100644 source/Public/Enable-SqlDscAudit.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 10aef9401..f652f01ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Invoke-SqlDscQuery` - `New-SqlDscAudit` - `Remove-SqlDscAudit` + - `Enable-SqlDscAudit` + - `Disable-SqlDscAudit` - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - Add new resource SqlServerAudit. diff --git a/source/Public/Disable-SqlDscAudit.ps1 b/source/Public/Disable-SqlDscAudit.ps1 new file mode 100644 index 000000000..57f55a4d8 --- /dev/null +++ b/source/Public/Disable-SqlDscAudit.ps1 @@ -0,0 +1,94 @@ +<# + .SYNOPSIS + Disables a server audit. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER AuditObject + Specifies a audit object to disable. + + .PARAMETER Name + Specifies the name of the server audit to be disabled. + + .PARAMETER Force + Specifies that the audit should be disabled with out any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s audits should be refreshed before + trying to disable the audit object. This is helpful when audits could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of audits it might be better to make + sure the **ServerObject** is recent enough, or pass in **AuditObject**. + + .OUTPUTS + None. +#> +function Disable-SqlDscAudit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'AuditObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Audit] + $AuditObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + + # TODO: this should use Get-SqlDscAudit + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + if ($Refresh.IsPresent) + { + # Make sure the audits are up-to-date to get any newly created audits. + $ServerObject.Audits.Refresh() + } + + $AuditObject = $ServerObject.Audits[$Name] + + if (-not $AuditObject) + { + $missingDatabaseMessage = $script:localizedData.Audit_Missing -f $Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingDatabaseMessage, + 'DSDA0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } + } + + $verboseDescriptionMessage = $script:localizedData.Audit_Disable_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Audit_Disable_ShouldProcessVerboseWarning -f $AuditObject.Name + $captionMessage = $script:localizedData.Audit_Disable_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + $AuditObject.Disable() + } +} diff --git a/source/Public/Enable-SqlDscAudit.ps1 b/source/Public/Enable-SqlDscAudit.ps1 new file mode 100644 index 000000000..49ee390b3 --- /dev/null +++ b/source/Public/Enable-SqlDscAudit.ps1 @@ -0,0 +1,94 @@ +<# + .SYNOPSIS + Enables a server audit. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER AuditObject + Specifies a audit object to enable. + + .PARAMETER Name + Specifies the name of the server audit to be enabled. + + .PARAMETER Force + Specifies that the audit should be enabled with out any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s audits should be refreshed before + trying to enable the audit object. This is helpful when audits could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of audits it might be better to make + sure the **ServerObject** is recent enough, or pass in **AuditObject**. + + .OUTPUTS + None. +#> +function Enable-SqlDscAudit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'AuditObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Audit] + $AuditObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + + # TODO: this should use Get-SqlDscAudit + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + if ($Refresh.IsPresent) + { + # Make sure the audits are up-to-date to get any newly created audits. + $ServerObject.Audits.Refresh() + } + + $AuditObject = $ServerObject.Audits[$Name] + + if (-not $AuditObject) + { + $missingDatabaseMessage = $script:localizedData.Audit_Missing -f $Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingDatabaseMessage, + 'ESDA0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } + } + + $verboseDescriptionMessage = $script:localizedData.Audit_Enable_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Audit_Enable_ShouldProcessVerboseWarning -f $AuditObject.Name + $captionMessage = $script:localizedData.Audit_Enable_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + $AuditObject.Enable() + } +} diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index a5f39cc1d..3e9f7e559 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -194,12 +194,14 @@ function New-SqlDscAudit $ConfirmPreference = 'None' } + # TODO: this should use Get-SqlDscAudit if ($Refresh.IsPresent) { # Make sure the audits are up-to-date to get any newly created audits. $ServerObject.Audits.Refresh() } + # TODO: this should use Get-SqlDscAudit if ($ServerObject.Audits[$Name]) { $missingDatabaseMessage = $script:localizedData.Audit_AlreadyPresent -f $Name diff --git a/source/Public/Remove-SqlDscAudit.ps1 b/source/Public/Remove-SqlDscAudit.ps1 index 2899d833d..77c66e7ae 100644 --- a/source/Public/Remove-SqlDscAudit.ps1 +++ b/source/Public/Remove-SqlDscAudit.ps1 @@ -57,6 +57,7 @@ function Remove-SqlDscAudit $ConfirmPreference = 'None' } + # TODO: this should use Get-SqlDscAudit if ($PSCmdlet.ParameterSetName -eq 'ServerObject') { if ($Refresh.IsPresent) @@ -74,7 +75,7 @@ function Remove-SqlDscAudit $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( $missingDatabaseMessage, - 'NSDA0001', # cspell: disable-line + 'RSDA0001', # cspell: disable-line [System.Management.Automation.ErrorCategory]::InvalidOperation, $DatabaseName ) diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 8712201b0..e21c2bfe6 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -47,10 +47,24 @@ ConvertFrom-StringData @' Audit_Add_ShouldProcessCaption = Add audit on instance Audit_AlreadyPresent = There is already an audit with the name '{0}'. - ## New-SqlDscAudit + ## Remove-SqlDscAudit, Enable-SqlDscAudit + Audit_Missing = There is no audit with the name '{0}'. + + ## Remove-SqlDscAudit Audit_Remove_ShouldProcessVerboseDescription = Removing the audit '{0}' on the instance '{1}'. Audit_Remove_ShouldProcessVerboseWarning = Are you sure you want you remove the audit '{0}'? # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Audit_Remove_ShouldProcessCaption = Remove audit on instance - Audit_Missing = There is no audit with the name '{0}'. + + ## Enable-SqlDscAudit + Audit_Enable_ShouldProcessVerboseDescription = Enabling the audit '{0}' on the instance '{1}'. + Audit_Enable_ShouldProcessVerboseWarning = Are you sure you want you enable the audit '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Audit_Enable_ShouldProcessCaption = Enable audit on instance + + ## Disable-SqlDscAudit + Audit_Disable_ShouldProcessVerboseDescription = Disabling the audit '{0}' on the instance '{1}'. + Audit_Disable_ShouldProcessVerboseWarning = Are you sure you want you disable the audit '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Audit_Disable_ShouldProcessCaption = Disable audit on instance '@ diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 index dda96feab..86268b73c 100644 --- a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -305,7 +305,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder } } @@ -320,7 +320,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockMethodCreateCallCount | Should -Be 1 } @@ -333,7 +333,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockMethodCreateCallCount | Should -Be 1 } @@ -346,7 +346,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockMethodCreateCallCount | Should -Be 0 } @@ -354,12 +354,12 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When passing parameter ServerObject over the pipeline' { It 'Should call the mocked method and have correct values in the object' { - $mockServerObject | New-SqlDscAudit -Path 'C:\Temp' -Name 'Log1' -Force + $mockServerObject | New-SqlDscAudit -Path (Get-TemporaryFolder) -Name 'Log1' -Force # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockMethodCreateCallCount | Should -Be 1 } @@ -376,7 +376,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { It 'Should throw the correct error' { $mockNewSqlDscAuditParameters = @{ ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Name = 'Log1' } @@ -384,7 +384,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $script:localizedData.Audit_PathParameterValueInvalid } - $mockErrorMessage = "Cannot validate argument on parameter 'Path'. " + ($mockErrorMessage -f 'C:\Temp') + $mockErrorMessage = "Cannot validate argument on parameter 'Path'. " + ($mockErrorMessage -f (Get-TemporaryFolder)) { New-SqlDscAudit @mockNewSqlDscAuditParameters } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -422,7 +422,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -437,7 +437,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.MaximumFileSize | Should -Be 1000 $mockCreateAuditObject.MaximumFileSizeUnit | Should -Be 'Mb' @@ -477,7 +477,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -492,7 +492,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.MaximumFiles | Should -Be 2 $mockMethodCreateCallCount | Should -Be 1 @@ -531,7 +531,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -546,7 +546,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.MaximumFiles | Should -Be 2 $mockCreateAuditObject.ReserveDiskSpace | Should -BeTrue @@ -560,7 +560,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.MaximumFiles | Should -Be 2 $mockCreateAuditObject.ReserveDiskSpace | Should -BeFalse @@ -601,7 +601,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -616,7 +616,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.MaximumRolloverFiles | Should -Be 2 $mockMethodCreateCallCount | Should -Be 1 @@ -655,7 +655,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -670,7 +670,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.Guid | Should -Be 'b5962b93-a359-42ef-bf1e-193e8a5f6222' $mockMethodCreateCallCount | Should -Be 1 @@ -721,7 +721,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -747,7 +747,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.OnFailure | Should -Be $MockOnFailureValue $mockMethodCreateCallCount | Should -Be 1 @@ -787,7 +787,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -802,7 +802,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.QueueDelay | Should -Be 1000 $mockMethodCreateCallCount | Should -Be 1 @@ -841,7 +841,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -856,7 +856,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' $mockCreateAuditObject.DestinationType | Should -Be 'File' - $mockCreateAuditObject.FilePath | Should -Be 'C:\Temp' + $mockCreateAuditObject.FilePath | Should -Be (Get-TemporaryFolder) $mockCreateAuditObject.Filter | Should -Be "([server_principal_name] like '%ADMINISTRATOR'" $mockMethodCreateCallCount | Should -Be 1 @@ -889,7 +889,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Path = 'C:\Temp' + Path = Get-TemporaryFolder Force = $true } } @@ -903,7 +903,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $newSqlDscAuditResult.Name | Should -Be 'Log1' $newSqlDscAuditResult.DestinationType | Should -Be 'File' - $newSqlDscAuditResult.FilePath | Should -Be 'C:\Temp' + $newSqlDscAuditResult.FilePath | Should -Be (Get-TemporaryFolder) $mockMethodCreateCallCount | Should -Be 1 } From de075ec9ed080e27f0d3b3519334d2d531f28bef Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 7 Aug 2022 13:03:20 +0200 Subject: [PATCH 21/71] Fix *-SqlDscAudit and tests --- CHANGELOG.md | 2 + source/Public/Disable-SqlDscAudit.ps1 | 42 +- source/Public/Enable-SqlDscAudit.ps1 | 42 +- source/Public/Get-SqlDscAudit.ps1 | 70 ++ source/Public/New-SqlDscAudit.ps1 | 76 +- source/Public/Remove-SqlDscAudit.ps1 | 42 +- source/Public/Set-SqlDscAudit.ps1 | 330 +++++++ source/en-US/SqlServerDsc.strings.psd1 | 12 +- .../Unit/Public/Disable-SqlDscAudit.Tests.ps1 | 190 ++++ .../Unit/Public/Enable-SqlDscAudit.Tests.ps1 | 190 ++++ tests/Unit/Public/Get-SqlDscAudit.Tests.ps1 | 120 +++ tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 83 +- .../Unit/Public/Remove-SqlDscAudit.Tests.ps1 | 45 +- tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 | 814 ++++++++++++++++++ 14 files changed, 1861 insertions(+), 197 deletions(-) create mode 100644 source/Public/Get-SqlDscAudit.ps1 create mode 100644 source/Public/Set-SqlDscAudit.ps1 create mode 100644 tests/Unit/Public/Disable-SqlDscAudit.Tests.ps1 create mode 100644 tests/Unit/Public/Enable-SqlDscAudit.Tests.ps1 create mode 100644 tests/Unit/Public/Get-SqlDscAudit.Tests.ps1 create mode 100644 tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index f652f01ce..c3612f51a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Get-SqlDscServerPermission` - `Set-SqlDscServerPermission` - `Invoke-SqlDscQuery` + - `Get-SqlDscAudit` - `New-SqlDscAudit` + - `Set-SqlDscAudit` - `Remove-SqlDscAudit` - `Enable-SqlDscAudit` - `Disable-SqlDscAudit` diff --git a/source/Public/Disable-SqlDscAudit.ps1 b/source/Public/Disable-SqlDscAudit.ps1 index 57f55a4d8..c77eef43d 100644 --- a/source/Public/Disable-SqlDscAudit.ps1 +++ b/source/Public/Disable-SqlDscAudit.ps1 @@ -6,7 +6,7 @@ Specifies current server connection object. .PARAMETER AuditObject - Specifies a audit object to disable. + Specifies an audit object to disable. .PARAMETER Name Specifies the name of the server audit to be disabled. @@ -21,6 +21,19 @@ But on instances with a large amount of audits it might be better to make sure the **ServerObject** is recent enough, or pass in **AuditObject**. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $auditObject = $sqlServerObject | Get-SqlDscAudit -Name 'MyFileAudit' + $auditObject | Disable-SqlDscAudit + + Disables the audit named **MyFileAudit**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | Disable-SqlDscAudit -Name 'MyFileAudit' + + Disables the audit named **MyFileAudit**. + .OUTPUTS None. #> @@ -57,30 +70,17 @@ function Disable-SqlDscAudit $ConfirmPreference = 'None' } - # TODO: this should use Get-SqlDscAudit if ($PSCmdlet.ParameterSetName -eq 'ServerObject') { - if ($Refresh.IsPresent) - { - # Make sure the audits are up-to-date to get any newly created audits. - $ServerObject.Audits.Refresh() + $getSqlDscAuditParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' } - $AuditObject = $ServerObject.Audits[$Name] - - if (-not $AuditObject) - { - $missingDatabaseMessage = $script:localizedData.Audit_Missing -f $Name - - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - $missingDatabaseMessage, - 'DSDA0001', # cspell: disable-line - [System.Management.Automation.ErrorCategory]::InvalidOperation, - $DatabaseName - ) - ) - } + # If this command does not find the audit it will throw an exception. + $AuditObject = Get-SqlDscAudit @getSqlDscAuditParameters } $verboseDescriptionMessage = $script:localizedData.Audit_Disable_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName diff --git a/source/Public/Enable-SqlDscAudit.ps1 b/source/Public/Enable-SqlDscAudit.ps1 index 49ee390b3..6e6a37a26 100644 --- a/source/Public/Enable-SqlDscAudit.ps1 +++ b/source/Public/Enable-SqlDscAudit.ps1 @@ -6,7 +6,7 @@ Specifies current server connection object. .PARAMETER AuditObject - Specifies a audit object to enable. + Specifies an audit object to enable. .PARAMETER Name Specifies the name of the server audit to be enabled. @@ -21,6 +21,19 @@ But on instances with a large amount of audits it might be better to make sure the **ServerObject** is recent enough, or pass in **AuditObject**. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $auditObject = $sqlServerObject | Get-SqlDscAudit -Name 'MyFileAudit' + $auditObject | Enable-SqlDscAudit + + Enables the audit named **MyFileAudit**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | Enable-SqlDscAudit -Name 'MyFileAudit' + + Enables the audit named **MyFileAudit**. + .OUTPUTS None. #> @@ -57,30 +70,17 @@ function Enable-SqlDscAudit $ConfirmPreference = 'None' } - # TODO: this should use Get-SqlDscAudit if ($PSCmdlet.ParameterSetName -eq 'ServerObject') { - if ($Refresh.IsPresent) - { - # Make sure the audits are up-to-date to get any newly created audits. - $ServerObject.Audits.Refresh() + $getSqlDscAuditParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' } - $AuditObject = $ServerObject.Audits[$Name] - - if (-not $AuditObject) - { - $missingDatabaseMessage = $script:localizedData.Audit_Missing -f $Name - - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - $missingDatabaseMessage, - 'ESDA0001', # cspell: disable-line - [System.Management.Automation.ErrorCategory]::InvalidOperation, - $DatabaseName - ) - ) - } + # If this command does not find the audit it will throw an exception. + $AuditObject = Get-SqlDscAudit @getSqlDscAuditParameters } $verboseDescriptionMessage = $script:localizedData.Audit_Enable_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName diff --git a/source/Public/Get-SqlDscAudit.ps1 b/source/Public/Get-SqlDscAudit.ps1 new file mode 100644 index 000000000..4a8943d6f --- /dev/null +++ b/source/Public/Get-SqlDscAudit.ps1 @@ -0,0 +1,70 @@ +<# + .SYNOPSIS + Get server audit. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER Name + Specifies the name of the server audit to get. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s audits should be refreshed before + trying get the audit object. This is helpful when audits could have been + modified outside of the **ServerObject**, for example through T-SQL. But + on instances with a large amount of audits it might be better to make + sure the **ServerObject** is recent enough, or pass in **AuditObject**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | Get-SqlDscAudit -Name 'MyFileAudit' + + Get the audit named **MyFileAudit**. + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.Audit]`. +#> +function Get-SqlDscAudit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType([Microsoft.SqlServer.Management.Smo.Audit])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + if ($Refresh.IsPresent) + { + # Make sure the audits are up-to-date to get any newly created audits. + $ServerObject.Audits.Refresh() + } + + $auditObject = $ServerObject.Audits[$Name] + + if (-not $AuditObject) + { + $missingAuditMessage = $script:localizedData.Audit_Missing -f $Name + + $writeErrorParameters = @{ + Message = $missingAuditMessage + Category = 'InvalidOperation' + ErrorId = 'GSDA0001' # cspell: disable-line + TargetObject = $Name + } + + Write-Error @writeErrorParameters + } + + return $auditObject +} diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index 3e9f7e559..1fe067f0a 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -35,10 +35,6 @@ on instances with a large amount of audits it might be better to make sure the ServerObject is recent enough. - .PARAMETER OperatorAudit - Specifies if auditing will capture Microsoft support engineers operations - during support requests. Applies to Azure SQL Managed Instance only. - .PARAMETER Type Specifies the log location where the audit should write to. This can be SecurityLog or ApplicationLog. @@ -47,8 +43,8 @@ Specifies the location where te log files wil be placed. .PARAMETER ReserveDiskSpace - Specifies if the needed file space should be reserved. only needed - when writing to a file log. + Specifies if the needed file space should be reserved. To use this parameter + the parameter **MaximumFiles** must also be used. .PARAMETER MaximumFiles Specifies the number of files on disk. @@ -67,6 +63,24 @@ `[Microsoft.SqlServer.Management.Smo.Audit]` is passing parameter **PassThru**, otherwise none. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | New-SqlDscAudit -Name 'MyFileAudit' -Path 'E:\auditFolder' + + Create a new file audit named **MyFileAudit**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | New-SqlDscAudit -Name 'MyAppLogAudit' -Type 'ApplicationLog' + + Create a new application log audit named **MyAppLogAudit**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | New-SqlDscAudit -Name 'MyFileAudit' -Path 'E:\auditFolder' -PassThru + + Create a new file audit named **MyFileAudit** and returns the Audit object. + .NOTES This command has the confirm impact level set to medium since an audit is created but by default is is not enabled. @@ -179,36 +193,27 @@ function New-SqlDscAudit $MaximumRolloverFiles ) - <# - TODO: Skapa Enable-SqlDscAudit eller Disable-SqlDscAudit - - TODO: Skapa Set-SqlDscAudit - - TODO: Skapa Remove-SqlDscAudit - - TODO: Skapa Get-SqlDscAudit som ska returnera alla Audit's om man inte - anger ett Filter-scriptblock - #> if ($Force.IsPresent) { $ConfirmPreference = 'None' } - # TODO: this should use Get-SqlDscAudit - if ($Refresh.IsPresent) - { - # Make sure the audits are up-to-date to get any newly created audits. - $ServerObject.Audits.Refresh() + $getSqlDscAuditParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'SilentlyContinue' } - # TODO: this should use Get-SqlDscAudit - if ($ServerObject.Audits[$Name]) + $auditObject = Get-SqlDscAudit @getSqlDscAuditParameters + + if ($auditObject) { - $missingDatabaseMessage = $script:localizedData.Audit_AlreadyPresent -f $Name + $auditAlreadyPresentMessage = $script:localizedData.Audit_AlreadyPresent -f $Name $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( - $missingDatabaseMessage, + $auditAlreadyPresentMessage, 'NSDA0001', # cspell: disable-line [System.Management.Automation.ErrorCategory]::InvalidOperation, $DatabaseName @@ -216,10 +221,6 @@ function New-SqlDscAudit ) } - # TODO: This is for Set-SqlDscAudit - # $auditObject = $ServerObject.Audits[$Name] - # $auditObject.Refresh() - $auditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($ServerObject, $Name) $queryType = switch ($PSCmdlet.ParameterSetName) @@ -255,15 +256,6 @@ function New-SqlDscAudit $auditObject.MaximumFileSizeUnit = $queryMaximumFileSizeUnit } - <# - TODO: For Set-SqlDscAudit: Switching between MaximumFiles and MaximumRolloverFiles must - run alter() between. - - $ServerObject.Audits['File1'].MaximumRolloverFiles = 0 - $ServerObject.Audits['File1'].Alter() - $ServerObject.Audits['File1'].MaximumFiles = 1 - $ServerObject.Audits['File1'].Alter() - #> if ($PSCmdlet.ParameterSetName -in @('FileWithMaxFiles', 'FileWithSizeAndMaxFiles')) { $auditObject.MaximumFiles = $MaximumFiles @@ -307,10 +299,10 @@ function New-SqlDscAudit if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) { $auditObject.Create() - } - if ($PassThru.IsPresent) - { - return $auditObject + if ($PassThru.IsPresent) + { + return $auditObject + } } } diff --git a/source/Public/Remove-SqlDscAudit.ps1 b/source/Public/Remove-SqlDscAudit.ps1 index 77c66e7ae..0b51fbc8e 100644 --- a/source/Public/Remove-SqlDscAudit.ps1 +++ b/source/Public/Remove-SqlDscAudit.ps1 @@ -6,7 +6,7 @@ Specifies current server connection object. .PARAMETER AuditObject - Specifies a audit object to remove. + Specifies an audit object to remove. .PARAMETER Name Specifies the name of the server audit to be removed. @@ -21,6 +21,19 @@ But on instances with a large amount of audits it might be better to make sure the **ServerObject** is recent enough, or pass in **AuditObject**. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $auditObject = $sqlServerObject | Get-SqlDscAudit -Name 'MyFileAudit' + $auditObject | Remove-SqlDscAudit + + Removes the audit named **MyFileAudit**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | Remove-SqlDscAudit -Name 'MyFileAudit' + + Removes the audit named **MyFileAudit**. + .OUTPUTS None. #> @@ -57,30 +70,17 @@ function Remove-SqlDscAudit $ConfirmPreference = 'None' } - # TODO: this should use Get-SqlDscAudit if ($PSCmdlet.ParameterSetName -eq 'ServerObject') { - if ($Refresh.IsPresent) - { - # Make sure the audits are up-to-date to get any newly created audits. - $ServerObject.Audits.Refresh() + $getSqlDscAuditParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' } - $AuditObject = $ServerObject.Audits[$Name] - - if (-not $AuditObject) - { - $missingDatabaseMessage = $script:localizedData.Audit_Missing -f $Name - - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - $missingDatabaseMessage, - 'RSDA0001', # cspell: disable-line - [System.Management.Automation.ErrorCategory]::InvalidOperation, - $DatabaseName - ) - ) - } + # If this command does not find the audit it will throw an exception. + $AuditObject = Get-SqlDscAudit @getSqlDscAuditParameters } $verboseDescriptionMessage = $script:localizedData.Audit_Remove_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName diff --git a/source/Public/Set-SqlDscAudit.ps1 b/source/Public/Set-SqlDscAudit.ps1 new file mode 100644 index 000000000..6087579ad --- /dev/null +++ b/source/Public/Set-SqlDscAudit.ps1 @@ -0,0 +1,330 @@ +<# + .SYNOPSIS + Updates a server audit. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER AuditObject + Specifies an audit object to update. + + .PARAMETER Name + Specifies the name of the server audit to be updated. + + .PARAMETER Filter + Specifies the filter that should be used on the audit. See [predicate expression](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql) + how to write the syntax for the filter. + + .PARAMETER OnFailure + Specifies what should happen when writing events to the store fails. + This can be 'Continue', 'FailOperation', or 'Shutdown'. + + .PARAMETER QueueDelay + Specifies the maximum delay before a event is written to the store. + When set to low this could impact server performance. + When set to high events could be missing when a server crashes. + + .PARAMETER AuditGuid + Specifies the GUID found in the mirrored database. To support scenarios such + as database mirroring an audit needs a specific GUID. + + .PARAMETER Force + Specifies that the audit should be updated with out any confirmation. + + .PARAMETER Refresh + Specifies that the audit object should be refreshed before updating. This + is helpful when audits could have been modified outside of the **ServerObject**, + for example through T-SQL. But on instances with a large amount of audits + it might be better to make sure the ServerObject is recent enough. + + .PARAMETER FilePath + Specifies the location where te log files wil be placed. + + .PARAMETER ReserveDiskSpace + Specifies if the needed file space should be reserved. To use this parameter + the parameter **MaximumFiles** must also be used. + + .PARAMETER MaximumFiles + Specifies the number of files on disk. + + .PARAMETER MaximumFileSize + Specifies the maximum file size in units by parameter MaximumFileSizeUnit. + + .PARAMETER MaximumFileSizeUnit + Specifies the unit that is used for the file size. this can be KB, MB or GB. + + .PARAMETER MaximumRolloverFiles + Specifies the amount of files on disk before SQL Server starts reusing + the files. If not specified then it is set to unlimited. + + .OUTPUTS + `[Microsoft.SqlServer.Management.Smo.Audit]` is passing parameter **PassThru**, + otherwise none. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | Set-SqlDscAudit -Name 'MyFileAudit' -Path 'E:\auditFolder' -QueueDelay 1000 + + Updates the file audit named **MyFileAudit** by setting the path to ''E:\auditFolder' + and the queue delay to 1000. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | New-SqlDscAudit -Name 'MyAppLogAudit' -QueueDelay 1000 + + Updates the application log audit named **MyAppLogAudit** by setting the + queue delay to 1000. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $sqlServerObject | Set-SqlDscAudit -Name 'MyFileAudit' -Path 'E:\auditFolder' -QueueDelay 1000 -PassThru + + Updates the file audit named **MyFileAudit** by setting the path to ''E:\auditFolder' + and the queue delay to 1000, and returns the Audit object. + + .NOTES + This command has the confirm impact level set to high since an audit is + unknown to be enable at the point when the command is issued. + + See the SQL Server documentation for more information for the possible + parameter values to pass to this command: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-server-audit-transact-sql +#> +function Set-SqlDscAudit +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [OutputType([Microsoft.SqlServer.Management.Smo.Audit])] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSize', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithMaxFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithMaxRolloverFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'AuditObject', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSize', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithMaxFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithMaxRolloverFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Audit] + $AuditObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $AuditFilter, + + [Parameter()] + [ValidateSet('Continue', 'FailOperation', 'Shutdown')] + [System.String] + $OnFailure, + + [Parameter()] + [ValidateRange(1000, 2147483647)] + [System.UInt32] + $QueueDelay, + + [Parameter()] + [ValidatePattern('^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] + [System.String] + $AuditGuid, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Refresh, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter(ParameterSetName = 'ServerObject')] + [Parameter(ParameterSetName = 'ServerObjectWithSize')] + [Parameter(ParameterSetName = 'ServerObjectWithMaxFiles')] + [Parameter(ParameterSetName = 'ServerObjectWithMaxRolloverFiles')] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles')] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles')] + [Parameter(ParameterSetName = 'AuditObject')] + [Parameter(ParameterSetName = 'AuditObjectWithSize')] + [Parameter(ParameterSetName = 'AuditObjectWithMaxFiles')] + [Parameter(ParameterSetName = 'AuditObjectWithMaxRolloverFiles')] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles')] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles')] + [ValidateScript({ + if (-not (Test-Path -Path $_)) + { + throw ($script:localizedData.Audit_PathParameterValueInvalid -f $_) + } + + return $true + })] + [System.String] + $Path, + + [Parameter(ParameterSetName = 'ServerObjectWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [ValidateRange(2, 2147483647)] + [System.UInt32] + $MaximumFileSize, + + [Parameter(ParameterSetName = 'ServerObjectWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSize', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [ValidateSet('Megabyte', 'Gigabyte', 'Terabyte')] + [System.String] + $MaximumFileSizeUnit, + + [Parameter(ParameterSetName = 'ServerObjectWithMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithMaxFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles', Mandatory = $true)] + [System.UInt32] + $MaximumFiles, + + [Parameter(ParameterSetName = 'ServerObjectWithMaxFiles')] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxFiles')] + [Parameter(ParameterSetName = 'AuditObjectWithMaxFiles')] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles')] + [System.Management.Automation.SwitchParameter] + $ReserveDiskSpace, + + [Parameter(ParameterSetName = 'ServerObjectWithMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithMaxRolloverFiles', Mandatory = $true)] + [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] + [ValidateRange(0, 2147483647)] + [System.UInt32] + $MaximumRolloverFiles + ) + + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + $getSqlDscAuditParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' + } + + $AuditObject = Get-SqlDscAudit @getSqlDscAuditParameters + } + + if ($Refresh.IsPresent) + { + $AuditObject.Refresh() + } + + $verboseDescriptionMessage = $script:localizedData.Audit_Update_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Audit_Update_ShouldProcessVerboseWarning -f $AuditObject.Name + $captionMessage = $script:localizedData.Audit_Update_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + if ($PSBoundParameters.ContainsKey('Path')) + { + $AuditObject.FilePath = $Path + } + + if ($PSCmdlet.ParameterSetName -match 'WithSize') + { + $queryMaximumFileSizeUnit = ( + @{ + Megabyte = 'MB' + Gigabyte = 'GB' + Terabyte = 'TB' + } + ).$MaximumFileSizeUnit + + $AuditObject.MaximumFileSize = $MaximumFileSize + $AuditObject.MaximumFileSizeUnit = $queryMaximumFileSizeUnit + } + + if ($PSCmdlet.ParameterSetName -match 'MaxFiles') + { + if ($AuditObject.MaximumRolloverFiles) + { + # Switching to MaximumFiles instead of MaximumRolloverFiles. + $AuditObject.MaximumRolloverFiles = 0 + + # Must run method Alter() before setting MaximumFiles. + $AuditObject.Alter() + } + + $AuditObject.MaximumFiles = $MaximumFiles + + if ($PSBoundParameters.ContainsKey('ReserveDiskSpace')) + { + $AuditObject.ReserveDiskSpace = $ReserveDiskSpace.IsPresent + } + } + + if ($PSCmdlet.ParameterSetName -match 'MaxRolloverFiles') + { + if ($AuditObject.MaximumFiles) + { + # Switching to MaximumRolloverFiles instead of MaximumFiles. + $AuditObject.MaximumFiles = 0 + + # Must run method Alter() before setting MaximumRolloverFiles. + $AuditObject.Alter() + } + + $AuditObject.MaximumRolloverFiles = $MaximumRolloverFiles + } + + if ($PSBoundParameters.ContainsKey('OnFailure')) + { + $AuditObject.OnFailure = $OnFailure + } + + if ($PSBoundParameters.ContainsKey('QueueDelay')) + { + $AuditObject.QueueDelay = $QueueDelay + } + + if ($PSBoundParameters.ContainsKey('AuditGuid')) + { + $AuditObject.Guid = $AuditGuid + } + + if ($PSBoundParameters.ContainsKey('AuditFilter')) + { + $AuditObject.Filter = $AuditFilter + } + + $AuditObject.Alter() + + if ($PassThru.IsPresent) + { + return $AuditObject + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index e21c2bfe6..22675159e 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -39,15 +39,23 @@ ConvertFrom-StringData @' ## Class DatabasePermission InvalidTypeForCompare = Invalid type in comparison. Expected type [{0}], but the type was [{1}]. (DP0001) - ## New-SqlDscAudit + ## New-SqlDscAudit, Set-SqlDscAudit Audit_PathParameterValueInvalid = The path '{0}' does not exist. Audit file can only be created in a path that already exist and where the SQL Server instance has permission to write. + + ## New-SqlDscAudit Audit_Add_ShouldProcessVerboseDescription = Adding the audit '{0}' on the instance '{1}'. Audit_Add_ShouldProcessVerboseWarning = Are you sure you want you add the audit '{0}'? # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Audit_Add_ShouldProcessCaption = Add audit on instance Audit_AlreadyPresent = There is already an audit with the name '{0}'. - ## Remove-SqlDscAudit, Enable-SqlDscAudit + ## Set-SqlDscAudit + Audit_Update_ShouldProcessVerboseDescription = Updating the audit '{0}' on the instance '{1}'. + Audit_Update_ShouldProcessVerboseWarning = Are you sure you want you update the audit '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Audit_Update_ShouldProcessCaption = Update audit on instance + + ## Get-SqlDscAudit Audit_Missing = There is no audit with the name '{0}'. ## Remove-SqlDscAudit diff --git a/tests/Unit/Public/Disable-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Disable-SqlDscAudit.Tests.ps1 new file mode 100644 index 000000000..fa171c110 --- /dev/null +++ b/tests/Unit/Public/Disable-SqlDscAudit.Tests.ps1 @@ -0,0 +1,190 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Disable-SqlDscAudit' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + MockParameterSetName = 'ServerObject' + MockExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObject' + MockExpectedParameters = '-AuditObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Disable-SqlDscAudit').ParameterSets | + Where-Object -FilterScript { + $_.Name -eq $mockParameterSetName + } | + Select-Object -Property @( + @{ + Name = 'ParameterSetName' + Expression = { $_.Name } + }, + @{ + Name = 'ParameterListAsString' + Expression = { $_.ToString() } + } + ) + + $result.ParameterSetName | Should -Be $MockParameterSetName + $result.ParameterListAsString | Should -Be $MockExpectedParameters + } + + Context 'When enabling an audit by ServerObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Disable' -Value { + $script:mockMethodDisableCallCount += 1 + } -PassThru -Force + } + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + } + } + + BeforeEach { + $script:mockMethodDisableCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Disable-SqlDscAudit -Confirm:$false @mockDefaultParameters + + $mockMethodDisableCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Disable-SqlDscAudit -Force @mockDefaultParameters + + $mockMethodDisableCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Disable-SqlDscAudit -WhatIf @mockDefaultParameters + + $mockMethodDisableCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockServerObject | Disable-SqlDscAudit -Name 'Log1' -Force + + $mockMethodDisableCallCount | Should -Be 1 + } + } + } + + Context 'When removing an audit by AuditObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Disable' -Value { + $script:mockMethodDisableCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + } + } + + BeforeEach { + $script:mockMethodDisableCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Disable-SqlDscAudit -Confirm:$false @mockDefaultParameters + + $mockMethodDisableCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Disable-SqlDscAudit -Force @mockDefaultParameters + + $mockMethodDisableCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Disable-SqlDscAudit -WhatIf @mockDefaultParameters + + $mockMethodDisableCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockAuditObject | Disable-SqlDscAudit -Force + + $mockMethodDisableCallCount | Should -Be 1 + } + } + } +} diff --git a/tests/Unit/Public/Enable-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Enable-SqlDscAudit.Tests.ps1 new file mode 100644 index 000000000..026cbe25d --- /dev/null +++ b/tests/Unit/Public/Enable-SqlDscAudit.Tests.ps1 @@ -0,0 +1,190 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Enable-SqlDscAudit' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + MockParameterSetName = 'ServerObject' + MockExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObject' + MockExpectedParameters = '-AuditObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Enable-SqlDscAudit').ParameterSets | + Where-Object -FilterScript { + $_.Name -eq $mockParameterSetName + } | + Select-Object -Property @( + @{ + Name = 'ParameterSetName' + Expression = { $_.Name } + }, + @{ + Name = 'ParameterListAsString' + Expression = { $_.ToString() } + } + ) + + $result.ParameterSetName | Should -Be $MockParameterSetName + $result.ParameterListAsString | Should -Be $MockExpectedParameters + } + + Context 'When enabling an audit by ServerObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Enable' -Value { + $script:mockMethodEnableCallCount += 1 + } -PassThru -Force + } + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + } + } + + BeforeEach { + $script:mockMethodEnableCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Enable-SqlDscAudit -Confirm:$false @mockDefaultParameters + + $mockMethodEnableCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Enable-SqlDscAudit -Force @mockDefaultParameters + + $mockMethodEnableCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Enable-SqlDscAudit -WhatIf @mockDefaultParameters + + $mockMethodEnableCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockServerObject | Enable-SqlDscAudit -Name 'Log1' -Force + + $mockMethodEnableCallCount | Should -Be 1 + } + } + } + + Context 'When removing an audit by AuditObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Enable' -Value { + $script:mockMethodEnableCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + } + } + + BeforeEach { + $script:mockMethodEnableCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Enable-SqlDscAudit -Confirm:$false @mockDefaultParameters + + $mockMethodEnableCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Enable-SqlDscAudit -Force @mockDefaultParameters + + $mockMethodEnableCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Enable-SqlDscAudit -WhatIf @mockDefaultParameters + + $mockMethodEnableCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockAuditObject | Enable-SqlDscAudit -Force + + $mockMethodEnableCallCount | Should -Be 1 + } + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Get-SqlDscAudit.Tests.ps1 new file mode 100644 index 000000000..fe1b3bb44 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscAudit.Tests.ps1 @@ -0,0 +1,120 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-SqlDscAudit' -Tag 'Public' { + Context 'When no audit exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{} + } -PassThru -Force + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + } + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Audit_Missing + } + } + + It 'Should throw the correct error' { + { Get-SqlDscAudit @mockDefaultParameters -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'Log1') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscAudit @mockDefaultParameters -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When getting a specific audit' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockServerObject = $mockServerObject | + Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { + return @{ + 'Log1' = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) + } + } -PassThru -Force + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + } + } + + It 'Should return the correct values' { + $result = Get-SqlDscAudit @mockDefaultParameters + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Audit' + $result.Name | Should -Be 'Log1' + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should return the correct values' { + $result = Get-SqlDscAudit @mockDefaultParameters + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Audit' + $result.Name | Should -Be 'Log1' + } + } + } +} diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 index 86268b73c..8f4d42bde 100644 --- a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -117,11 +117,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -206,11 +204,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -295,11 +291,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -412,11 +406,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -467,11 +459,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -521,11 +511,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -591,11 +579,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -645,11 +631,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -711,11 +695,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -777,11 +759,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -831,11 +811,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $script:mockCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -879,11 +857,9 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { return $mockNewCreateAuditObject } - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' $mockDefaultParameters = @{ @@ -911,12 +887,11 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When the audit already exist' { BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{ - 'Log1' = New-Object -TypeName Object - } - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' + } + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockDefaultParameters = @{ ServerObject = $mockServerObject diff --git a/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 index d38ec92e5..3c977db46 100644 --- a/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 @@ -80,18 +80,15 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' $mockServerObject.InstanceName = 'TestInstance' - $mockServerObject = $mockServerObject | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{ - 'Log1' = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( - $mockServerObject, - 'Log1' - ) | - Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExist' -Value { - $script:mockMethodDropIfExistCallCount += 1 - } -PassThru -Force - } - } -PassThru -Force + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExist' -Value { + $script:mockMethodDropIfExistCallCount += 1 + } -PassThru -Force + } $mockDefaultParameters = @{ ServerObject = $mockServerObject @@ -190,28 +187,4 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { } } } - - Context 'When the audit does not exist' { - BeforeAll { - $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{} - } -PassThru -Force - - $mockDefaultParameters = @{ - ServerObject = $mockServerObject - Name = 'Log1' - Force = $true - } - } - - It 'Should throw the correct error' { - $mockErrorMessage = InModuleScope -ScriptBlock { - $script:localizedData.Audit_Missing - } - - { Remove-SqlDscAudit @mockDefaultParameters } | - Should -Throw -ExpectedMessage ($mockErrorMessage -f 'Log1') - } - } } diff --git a/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 new file mode 100644 index 000000000..cdf3a3ff6 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 @@ -0,0 +1,814 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Set-SqlDscAudit' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + MockParameterSetName = 'ServerObject' + MockExpectedParameters = '-ServerObject -Name [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'ServerObjectWithSize' + MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'ServerObjectWithMaxFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'ServerObjectWithMaxRolloverFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'ServerObjectWithSizeAndMaxFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'ServerObjectWithSizeAndMaxRolloverFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObject' + MockExpectedParameters = '-AuditObject [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObjectWithSize' + MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObjectWithMaxFiles' + MockExpectedParameters = '-AuditObject -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObjectWithMaxRolloverFiles' + MockExpectedParameters = '-AuditObject -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObjectWithSizeAndMaxFiles' + MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + } + @{ + MockParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles' + MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscAudit').ParameterSets | + Where-Object -FilterScript { + $_.Name -eq $mockParameterSetName + } | + Select-Object -Property @( + @{ + Name = 'ParameterSetName' + Expression = { $_.Name } + }, + @{ + Name = 'ParameterListAsString' + Expression = { $_.ToString() } + } + ) + + $result.ParameterSetName | Should -Be $MockParameterSetName + $result.ParameterListAsString | Should -Be $MockExpectedParameters + } + + Context 'When setting an audit by an ServerObject' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + Mock -CommandName Get-SqlDscAudit -MockWith { + <# + The Audit object is created in the script scope so that the + properties can be validated. + #> + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + return $script:mockAuditObject + } + + $mockDefaultParameters = @{ + ServerObject = $mockServerObject + Name = 'Log1' + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -Confirm:$false -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -Force -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -WhatIf -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -BeNullOrEmpty + + $mockMethodAlterCallCount | Should -Be 0 + } + } + + Context 'When passing parameter ServerObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockServerObject | Set-SqlDscAudit -Name 'Log1' -QueueDelay 1000 -Force + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + } + + Context 'When setting an audit by an AuditObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + } + + BeforeEach { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + } + + $script:mockMethodAlterCallCount = 0 + } + + Context 'When using parameter Confirm with value $false' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -Confirm:$false -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -Force -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -WhatIf -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -BeNullOrEmpty + + $mockMethodAlterCallCount | Should -Be 0 + } + } + + Context 'When passing parameter AuditObject over the pipeline' { + It 'Should call the mocked method and have correct values in the object' { + $mockAuditObject | Set-SqlDscAudit -QueueDelay 1000 -Force + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + } + + Context 'When adding an file audit and passing an invalid path' { + BeforeAll { + Mock -CommandName Test-Path -MockWith { + return $false + } + } + + It 'Should throw the correct error' { + $mockNewSqlDscAuditParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Path = Get-TemporaryFolder + Name = 'Log1' + } + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Audit_PathParameterValueInvalid + } + + $mockErrorMessage = "Cannot validate argument on parameter 'Path'. " + ($mockErrorMessage -f (Get-TemporaryFolder)) + + { Set-SqlDscAudit @mockNewSqlDscAuditParameters } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + Context 'When passing file audit optional parameter Path' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -Path (Get-TemporaryFolder) @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.FilePath | Should -Be (Get-TemporaryFolder) + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing file audit optional parameters MaximumFileSize and MaximumFileSizeUnit' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -MaximumFileSize 1000 -MaximumFileSizeUnit 'Megabyte' @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumFileSize | Should -Be 1000 + $mockAuditObject.MaximumFileSizeUnit | Should -Be 'Mb' + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing file audit optional parameters MaximumFiles' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -MaximumFiles 2 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumFiles | Should -Be 2 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing file audit optional parameters MaximumFiles and ReserveDiskSpace' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -MaximumFiles 2 -ReserveDiskSpace @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumFiles | Should -Be 2 + $mockAuditObject.ReserveDiskSpace | Should -BeTrue + + $mockMethodAlterCallCount | Should -Be 1 + } + + Context 'When ReserveDiskSpace is set to $false' { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -MaximumFiles 2 -ReserveDiskSpace:$false @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumFiles | Should -Be 2 + $mockAuditObject.ReserveDiskSpace | Should -BeFalse + + $mockMethodAlterCallCount | Should -Be 1 + } + } + } + + Context 'When passing file audit optional parameters MaximumRolloverFiles' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -MaximumRolloverFiles 2 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumRolloverFiles | Should -Be 2 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing audit optional parameter AuditGuid' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.Guid | Should -Be 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + + $mockMethodAlterCallCount | Should -Be 1 + } + + Context 'When passing an invalid GUID' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Cannot validate argument on parameter ''AuditGuid''. The argument "not a guid" does not match the "^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$" pattern.' + + # Escape bracket so that Should -Throw works. + $mockErrorMessage = $mockErrorMessage -replace '\[', '`[' + + { Set-SqlDscAudit -AuditGuid 'not a guid' @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage + '*') + } + } + } + + Context 'When passing audit optional parameter OnFailure' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + Context 'When passing the value ' -ForEach @( + @{ + MockOnFailureValue = 'Continue' + } + @{ + MockOnFailureValue = 'FailOperation' + } + @{ + MockOnFailureValue = 'ShutDown' + } + ) { + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -OnFailure $MockOnFailureValue @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.OnFailure | Should -Be $MockOnFailureValue + + $mockMethodAlterCallCount | Should -Be 1 + } + } + } + + Context 'When passing audit optional parameter QueueDelay' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -QueueDelay 1000 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing audit optional parameter Filter' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -AuditFilter "([server_principal_name] like '%ADMINISTRATOR'" @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.Filter | Should -Be "([server_principal_name] like '%ADMINISTRATOR'" + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing optional parameter PassThru' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + $newSqlDscAuditResult = Set-SqlDscAudit -QueueDelay 1000 -PassThru @mockDefaultParameters + + $newSqlDscAuditResult.Name | Should -Be 'Log1' + $newSqlDscAuditResult.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + } + } + + Context 'When passing optional parameter Refresh' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Refresh' -Value { + $script:mockMethodRefreshCallCount += 1 + } -PassThru -Force + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + $script:mockMethodRefreshCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + Set-SqlDscAudit -QueueDelay 1000 -Refresh @mockDefaultParameters + + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.QueueDelay | Should -Be 1000 + + $mockMethodAlterCallCount | Should -Be 1 + $mockMethodRefreshCallCount | Should -Be 1 + } + } + + Context 'When switching from MaximumRolloverFiles to MaximumFiles' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $script:mockAuditObject.MaximumRolloverFiles = 10 + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + $mockAuditObject.MaximumRolloverFiles | Should -Be 10 -Because 'there has to be a value greater than 0 in the object that is passed to the command in this test' + + Set-SqlDscAudit -MaximumFiles 2 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumRolloverFiles | Should -Be 0 + $mockAuditObject.MaximumFiles | Should -Be 2 + + $mockMethodAlterCallCount | Should -Be 2 -Because 'the call to Alter() need to happen twice, first to set MaximumRolloverFiles to 0, then another to set MaximumFiles to the new value' + } + } + + Context 'When switching from MaximumFiles to MaximumRolloverFiles' { + BeforeAll { + $script:mockAuditObject = $null + + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $mockServerObject, + 'Log1' + ) | + Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -PassThru -Force + + $script:mockAuditObject.MaximumFiles = 10 + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + } + + It 'Should call the mocked method and have correct values in the object' { + $mockAuditObject.MaximumFiles | Should -Be 10 -Because 'there has to be a value greater than 0 in the object that is passed to the command in this test' + + Set-SqlDscAudit -MaximumRolloverFiles 2 @mockDefaultParameters + + # This is the object created by the mock and modified by the command. + $mockAuditObject.Name | Should -Be 'Log1' + $mockAuditObject.MaximumFiles | Should -Be 0 + $mockAuditObject.MaximumRolloverFiles | Should -Be 2 + + $mockMethodAlterCallCount | Should -Be 2 -Because 'the call to Alter() need to happen twice, first to set MaximumFiles to 0, then another to set MaximumRolloverFiles to the new value' + } + } +} From 24d3247d43dab9f753714dba36a85277fb33b331 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 7 Aug 2022 13:22:30 +0200 Subject: [PATCH 22/71] Fix SMO to support Windows PowerShell --- tests/Unit/Stubs/SMO.cs | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index f5ff36fbd..c210614d2 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -898,25 +898,25 @@ public Audit(Microsoft.SqlServer.Management.Smo.Server server, System.String nam } // Property - public Microsoft.SqlServer.Management.Smo.Server Parent { get; set; } = null; - public System.DateTime? CreateDate { get; set; } = null; - public System.DateTime? DateLastModified { get; set; } = null; - public Microsoft.SqlServer.Management.Smo.AuditDestinationType? DestinationType { get; set; } = null; - public System.Boolean? Enabled { get; set; } = null; - public System.String FileName { get; set; } = null; - public System.String FilePath { get; set; } = null; - public System.String Filter { get; set; } = null; - public System.Guid? Guid { get; set; } = null; - public System.Int32? ID { get; set; } = null; - public System.Int32? MaximumFiles { get; set; } = null; - public System.Int32? MaximumFileSize { get; set; } = null; - public Microsoft.SqlServer.Management.Smo.AuditFileSizeUnit? MaximumFileSizeUnit { get; set; } = null; - public System.Int64? MaximumRolloverFiles { get; set; } = null; - public Microsoft.SqlServer.Management.Smo.OnFailureAction? OnFailure { get; set; } = null; - public System.Int32? QueueDelay { get; set; } = null; - public System.Boolean? ReserveDiskSpace { get; set; } = null; - public System.Int32? RetentionDays { get; set; } = null; - public System.String Name { get; set; } = null; + public Microsoft.SqlServer.Management.Smo.Server Parent { get; set; } + public System.DateTime? CreateDate { get; set; } + public System.DateTime? DateLastModified { get; set; } + public Microsoft.SqlServer.Management.Smo.AuditDestinationType? DestinationType { get; set; } + public System.Boolean? Enabled { get; set; } + public System.String FileName { get; set; } + public System.String FilePath { get; set; } + public System.String Filter { get; set; } + public System.Guid? Guid { get; set; } + public System.Int32? ID { get; set; } + public System.Int32? MaximumFiles { get; set; } + public System.Int32? MaximumFileSize { get; set; } + public Microsoft.SqlServer.Management.Smo.AuditFileSizeUnit? MaximumFileSizeUnit { get; set; } + public System.Int64? MaximumRolloverFiles { get; set; } + public Microsoft.SqlServer.Management.Smo.OnFailureAction? OnFailure { get; set; } + public System.Int32? QueueDelay { get; set; } + public System.Boolean? ReserveDiskSpace { get; set; } + public System.Int32? RetentionDays { get; set; } + public System.String Name { get; set; } // public Microsoft.SqlServer.Management.Smo.AbstractCollectionBase ParentCollection { get; set; } // public Microsoft.SqlServer.Management.Sdk.Sfc.Urn Urn { get; set; } // public Microsoft.SqlServer.Management.Smo.SqlPropertyCollection Properties { get; set; } @@ -924,8 +924,8 @@ public Audit(Microsoft.SqlServer.Management.Smo.Server server, System.String nam // public Microsoft.SqlServer.Management.Common.DatabaseEngineType DatabaseEngineType { get; set; } // public Microsoft.SqlServer.Management.Common.DatabaseEngineEdition DatabaseEngineEdition { get; set; } // public Microsoft.SqlServer.Management.Smo.ExecutionManager ExecutionManager { get; set; } - public System.Object UserData { get; set; } = null; - public Microsoft.SqlServer.Management.Smo.SqlSmoState? State { get; set; } = null; + public System.Object UserData { get; set; } + public Microsoft.SqlServer.Management.Smo.SqlSmoState? State { get; set; } } #endregion Public Classes From 79b581179599cc31112f1544402ec686dbaf8c9b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 7 Aug 2022 13:22:49 +0200 Subject: [PATCH 23/71] Revert parameter in SqlServerAudit --- .../DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 | 6 +++--- .../DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 index d3292b56c..5805dce8d 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 @@ -43,7 +43,7 @@ function Get-TargetResource [System.String] $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] [System.String] $DestinationType @@ -179,7 +179,7 @@ function Set-TargetResource [System.String] $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] [System.String] $DestinationType, @@ -628,7 +628,7 @@ function Test-TargetResource [System.String] $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] [System.String] $DestinationType, diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof index 5c6c5d92c..03cf2dcbb 100644 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof +++ b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof @@ -4,7 +4,7 @@ class DSC_SqlServerAudit : OMI_BaseResource [Key, Description("Specifies the host name of the SQL Server on which the instance exist.")] String ServerName; [Key, Description("Specifies the SQL instance in which the Audit exist.")] String InstanceName; [Key, Description("Specifies the name of the SQL audit to be added or removed.")] String Name; - [Required, Description("Specifies the location where the audit should write to."), ValueMap{"File", "SecurityLog", "ApplicationLog"}, Values{"File", "SecurityLog", "ApplicationLog"}] String DestinationType; + [Write, Description("Specifies the location where the audit should write to."), ValueMap{"File", "SecurityLog", "ApplicationLog"}, Values{"File", "SecurityLog", "ApplicationLog"}] String DestinationType; [Write, Description("Specifies the path where the audit files are stored when DestinationType is set to `File`.")] String FilePath; [Write, Description("Specifies if the audit should be enabled. Defaults to `$false`.")] Boolean Enabled; [Write, Description("Specifies the filter that should be used on the audit.")] String Filter; From 70f6afdbfab0abee41e0fd7d18ccc6bd3c5ba8f6 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 7 Aug 2022 13:30:31 +0200 Subject: [PATCH 24/71] Change parameter to LogType --- source/Public/New-SqlDscAudit.ps1 | 8 ++++---- tests/Unit/Public/New-SqlDscAudit.Tests.ps1 | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index 1fe067f0a..0f0de4f9c 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -35,7 +35,7 @@ on instances with a large amount of audits it might be better to make sure the ServerObject is recent enough. - .PARAMETER Type + .PARAMETER LogType Specifies the log location where the audit should write to. This can be SecurityLog or ApplicationLog. @@ -71,7 +71,7 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $sqlServerObject | New-SqlDscAudit -Name 'MyAppLogAudit' -Type 'ApplicationLog' + $sqlServerObject | New-SqlDscAudit -Name 'MyAppLogAudit' -LogType 'ApplicationLog' Create a new application log audit named **MyAppLogAudit**. @@ -143,7 +143,7 @@ function New-SqlDscAudit [Parameter(ParameterSetName = 'Log', Mandatory = $true)] [ValidateSet('SecurityLog', 'ApplicationLog')] [System.String] - $Type, + $LogType, [Parameter(ParameterSetName = 'File', Mandatory = $true)] [Parameter(ParameterSetName = 'FileWithSize', Mandatory = $true)] @@ -227,7 +227,7 @@ function New-SqlDscAudit { 'Log' { - $Type + $LogType } default diff --git a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 index 8f4d42bde..05447edff 100644 --- a/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/New-SqlDscAudit.Tests.ps1 @@ -49,7 +49,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { It 'Should have the correct parameters in parameter set ' -ForEach @( @{ MockParameterSetName = 'Log' - MockExpectedParameters = '-ServerObject -Name -Type [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-ServerObject -Name -LogType [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'File' @@ -124,7 +124,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject - Type = 'ApplicationLog' + LogType = 'ApplicationLog' Name = 'Log1' } } @@ -171,7 +171,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When passing parameter ServerObject over the pipeline' { It 'Should call the mocked method and have correct values in the object' { - $mockServerObject | New-SqlDscAudit -Type 'ApplicationLog' -Name 'Log1' -Force + $mockServerObject | New-SqlDscAudit -LogType 'ApplicationLog' -Name 'Log1' -Force # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' @@ -211,7 +211,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject - Type = 'SecurityLog' + LogType = 'SecurityLog' Name = 'Log1' } } @@ -258,7 +258,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { Context 'When passing parameter ServerObject over the pipeline' { It 'Should call the mocked method and have correct values in the object' { - $mockServerObject | New-SqlDscAudit -Type 'SecurityLog' -Name 'Log1' -Force + $mockServerObject | New-SqlDscAudit -LogType 'SecurityLog' -Name 'Log1' -Force # This is the object created by the mock and modified by the command. $mockCreateAuditObject.Name | Should -Be 'Log1' @@ -896,7 +896,7 @@ Describe 'New-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject Name = 'Log1' - Type = 'ApplicationLog' + LogType = 'ApplicationLog' Force = $true } } From 9c1e2b0504da8f27a2f638ebdb99180dc6ed2e5b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 7 Aug 2022 13:37:32 +0200 Subject: [PATCH 25/71] Fix AppVeyor.yml --- appveyor.yml | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ae722affc..42d5eea5e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,33 +23,27 @@ environment: #- TEST_CONFIGURATION: Integration_SQL2019 # DEBUG: See section on_finish last in this file on how to block build to keep RDP open. +# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request init: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) - { - return - } + #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) +# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request install: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) - { - return - } + #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } winrm quickconfig -quiet +# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request build_script: - pwsh: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) - { - return - } + #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } # Build the module ./build.ps1 -ResolveDependency -tasks build @@ -57,13 +51,11 @@ build_script: # DEBUG: Comment and un-comment integration tests as needed for the purpose of debugging. # Note that some integration tests depend on each other to work. See the README for more # information: https://github.com/dsccommunity/SqlServerDsc/blob/main/tests/Integration/README.md +# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request test_script: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) - { - return - } + #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $env:TEST_CONFIGURATION -PesterPath @( ### Run the integration tests in a specific group order. @@ -104,8 +96,8 @@ test_script: deploy: off -# DEBUG: Un-comment the following line so that build worker is kept up all of the 60 minutes. +# DEBUG: Un-comment the line "$blockRdp = $true" so that build worker is kept up all of the 60 minutes. on_finish: - ps: | - $blockRdp = $true - iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + #$blockRdp = $true + iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 21bb52113055e6df459456fbc220e9ba48afa6ed Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 7 Aug 2022 18:32:25 +0200 Subject: [PATCH 26/71] Fix SqlAudit --- CHANGELOG.md | 4 +- source/Classes/020.SqlAudit.ps1 | 396 +++++++++++++++++++++++++++++ source/en-US/SqlAudit.strings.psd1 | 13 + 3 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 source/Classes/020.SqlAudit.ps1 create mode 100644 source/en-US/SqlAudit.strings.psd1 diff --git a/CHANGELOG.md b/CHANGELOG.md index c3612f51a..2440781c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,8 +81,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Disable-SqlDscAudit` - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - - Add new resource SqlServerAudit. - - Add new resource SqlServerAuditSpecification. + - Add new resource SqlAudit. + - Add new resource SqlAuditSpecification. - CommonTestHelper - `Import-SqlModuleStub` - Added the optional parameter **PasThru** that, if used, will return the diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 new file mode 100644 index 000000000..75556d5ff --- /dev/null +++ b/source/Classes/020.SqlAudit.ps1 @@ -0,0 +1,396 @@ +<# + .SYNOPSIS + The `SqlAudit` DSC resource is used to create, modify, or remove + server audits. + + .DESCRIPTION + The `SqlAudit` DSC resource is used to create, modify, or remove + server audits. + + ## Requirements + + * Target machine must be running Windows Server 2012 or later. + * 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. + + ## 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+SqlAudit). + + ### `PSDscRunAsCredential` not supported + + The built-in property `PSDscRunAsCredential` does not work with class-based + resources that using advanced type like the parameter `Permission` does. + Use the parameter `Credential` instead of `PSDscRunAsCredential`. + + ### Using `Credential` property. + + SQL Authentication and Group Managed Service Accounts is not supported as + impersonation credentials. Currently only Windows Integrated Security is + supported to use as credentials. + + For Windows Authentication the username must either be provided with the User + Principal Name (UPN), e.g. 'username@domain.local' or if using non-domain + (for example a local Windows Server account) account the username must be + provided without the NetBIOS name, e.g. 'username'. The format 'DOMAIN\username' + will not work. + + See more information in [Credential Overview](https://github.com/dsccommunity/SqlServerDsc/wiki/CredentialOverview). + + .PARAMETER InstanceName + The name of the _SQL Server_ instance to be configured. Default value is + `'MSSQLSERVER'`. + + .PARAMETER Name + The name of the audit. + + .PARAMETER ServerName + The host name of the _SQL Server_ to be configured. Default value is the + current computer name. + + .PARAMETER LogType + Specifies the to which log an audit logs to. Mutually exclusive to parameter + **Path**. This can be set to `SecurityLog` or `ApplicationLog`. + + .PARAMETER Path + Specifies the destination path for a file audit. Mutually exclusive to parameter + **LogType**. + + .PARAMETER Filter + Specifies the filter that should be used on the audit. + + .PARAMETER MaximumFiles + Specifies the number of files on disk. Mutually exclusive to parameter + **MaximumRolloverFiles**. + + .PARAMETER MaximumFileSize + Specifies the maximum file size in units by parameter **MaximumFileSizeUnit**. + If this is specified the parameter **MaximumFileSizeUnit** must also be + specified. + + .PARAMETER MaximumFileSizeUnit + Specifies the unit that is used for the file size. this can be KB, MB or GB. + If this is specified the parameter **MaximumFileSize** must also be + specified. + + .PARAMETER MaximumRolloverFiles + Specifies the amount of files on disk before SQL Server starts reusing + the files. Mutually exclusive to parameter **MaximumFiles**. + + .PARAMETER OnFailure + Specifies what should happen when writing events to the store fails. + This can be `Continue`, `FailOperation`, or `Shutdown`. + + .PARAMETER QueueDelay + Specifies the maximum delay before a event is written to the store. + When set to low this could impact server performance. + When set to high events could be missing when a server crashes. + + .PARAMETER ReserveDiskSpace + Specifies if the needed file space should be reserved. only needed + when writing to a file log. + + .PARAMETER Enabled + Specifies if the audit should be enabled. Defaults to `$false`. + + .PARAMETER Ensure + Specifies if the server audit should be present or absent. If set to `Present` + the audit will be added if it does not exist, or updated if the audit exist. + If `Absent` then the audit will be removed from the server. Defaults to + `Present`. + + .PARAMETER Force + Specifies if it is allowed to re-create the server audit if a current audit + exist with the same name but of a different audit type. Defaults to `$false` + not allowing server audits to be re-created. + + .PARAMETER Credential + Specifies the credential to use to connect to the _SQL Server_ instance. + + If parameter **Credential'* is not provided then the resource instance is + run using the credential that runs the configuration. + + .PARAMETER Reasons + Returns the reason a property is not in desired state. + + .EXAMPLE + Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAudit -Method Get -Property @{ + ServerName = 'localhost' + InstanceName = 'SQL2017' + Credential = (Get-Credential -UserName 'myuser@company.local' -Message 'Password:') + Name = 'Log1' + } + + This example shows how to call the resource using Invoke-DscResource. +#> + +# TODO: verify RunAsCredential = 'NotSupported' - remove in comment-based help +[DscResource()] +class SqlAudit : ResourceBase +{ + <# + Property for holding the server connection object. + This should be an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] $sqlServerObject = $null + + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty()] + [ValidateSet('SecurityLog', 'ApplicationLog')] + [System.String] + $LogType + + # TODO: Must assert the path at run time + [DscProperty()] + [System.String] + $Path + + [DscProperty()] + [System.String] + $AuditFilter + + [DscProperty()] + [System.UInt32] + $MaximumFiles + + [DscProperty()] + [ValidateRange(2, 2147483647)] + [System.UInt32] + $MaximumFileSize + + [DscProperty()] + [ValidateSet('Megabyte', 'Gigabyte', 'Terabyte')] + [System.String] + $MaximumFileSizeUnit + + [DscProperty()] + [System.UInt32] + $MaximumRolloverFiles + + [DscProperty()] + [ValidateSet('Continue', 'FailOperation', 'Shutdown')] + [System.String] + $OnFailure + + [DscProperty()] + [ValidateRange(1000, 2147483647)] + [System.UInt32] + $QueueDelay + + [DscProperty()] + [ValidatePattern('^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')] + [System.String] + $AuditGuid + + [DscProperty()] + [System.Boolean] + $ReserveDiskSpace + + [DscProperty()] + [System.Boolean] + $Enabled + + [DscProperty()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present' + + [DscProperty()] + [System.Boolean] + $Force + + [DscProperty()] + [PSCredential] + $Credential + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + SqlAudit() : base () + { + # These properties will not be enforced. + $this.notEnforcedProperties = @( + 'ServerName' + 'InstanceName' + 'Name' + 'Credential' + ) + } + + [SqlAudit] Get() + { + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + [System.Boolean] Test() + { + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + TODO: This method can be moved to a parent class "SqlServerDscResource" that + instead inherits ResourceBase. Then this method does not need to be + duplicated. Make sure to create a localized strings file for the new + class. + The property 'sqlServerObject' should also be moved (but still be hidden). + #> + <# + Returns and reuses the server connection object. If the server connection + object does not exist a connection to the SQL Server instance will occur. + + This should return an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] GetServerObject() + { + if (-not $this.sqlServerObject) + { + $connectSqlDscDatabaseEngineParameters = @{ + ServerName = $this.ServerName + InstanceName = $this.InstanceName + } + + if ($this.Credential) + { + $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + } + + $this.sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + } + + return $this.sqlServerObject + } + + <# + Base method Get() call this method to get the current state as a hashtable. + The parameter properties will contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + $currentStateCredential = $null + + if ($this.Credential) + { + <# + This does not work, even if username is set, the method Get() will + return an empty PSCredential-object. Kept it here so it at least + return a Credential object. + #> + $currentStateCredential = [PSCredential]::new( + $this.Credential.UserName, + [SecureString]::new() + ) + } + + $currentState = @{ + Credential = $currentStateCredential + } + + Write-Verbose -Message ( + $this.localizedData.EvaluateServerPermissionForPrincipal -f @( + $properties.Name, + $properties.InstanceName + ) + ) + + $serverObject = $this.GetServerObject() + + $auditObject = $serverObject | + Get-SqlDscAudit -Name $this.Name -ErrorAction 'SilentlyContinue' + + # If permissions was returned, build the current permission array of [ServerPermission]. + if ($auditObject) + { + } + + return $currentState + } + + <# + Base method Set() call this method with the properties that should be + enforced are not in desired state. It is not called if all properties + are in desired state. The variable $properties contain the properties + that are not in desired state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + $serverObject = $this.GetServerObject() + + # if (-not $isLogin) + # { + # $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( + # $this.Name, + # $this.InstanceName + # ) + + # New-InvalidOperationException -Message $missingPrincipalMessage + # } + + } + + <# + Base method Assert() call this method with the properties that was assigned + a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + MutuallyExclusiveList1 = @( + 'MaximumFiles' + ) + MutuallyExclusiveList2 = @( + 'MaximumRolloverFiles' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + # Get all assigned permission properties. + $assignedSizeProperty = $properties.Keys.Where({ + $_ -in @( + 'MaximumFileSize', + 'MaximumFileSizeUnit' + ) + }) + + # TODO: Above count should be either 0 or 2, if 1 throw an error. + # if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) + # { + # $errorMessage = $this.localizedData.MustAssignOnePermissionProperty + + # New-InvalidArgumentException -ArgumentName 'Permission, PermissionToInclude, PermissionToExclude' -Message $errorMessage + # } + + # TODO: Test path + # if (-not (Test-Path -Path $_)) + # { + # throw ($script:localizedData.Audit_PathParameterValueInvalid -f $_) + # } + } +} diff --git a/source/en-US/SqlAudit.strings.psd1 b/source/en-US/SqlAudit.strings.psd1 new file mode 100644 index 000000000..318dacb8d --- /dev/null +++ b/source/en-US/SqlAudit.strings.psd1 @@ -0,0 +1,13 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource SqlPermission. +#> + +ConvertFrom-StringData @' + ## Strings overrides for the ResourceBase's default strings. + # None + + ## Strings directly used by the derived class SqlDatabasePermission. + #EvaluateServerPermissionForPrincipal = Evaluate the current permissions for the principal '{0}' on the instance '{1}'. (SP0001) +'@ From 4922e8e96b1140fe0ee71ed87b19393122d4796f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 15:13:01 +0200 Subject: [PATCH 27/71] Fix to commands --- CHANGELOG.md | 4 ++ source/Private/Get-DscProperty.ps1 | 10 ++++ source/Public/New-SqlDscAudit.ps1 | 7 ++- tests/Unit/Private/Get-DscProperty.Tests.ps1 | 62 ++++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2440781c4..0da34ac4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the PowerShell SqlServer stub module when a test has run. - SqlWindowsFirewall - Added integration tests for SqlWindowsFirewall ([issue #747](https://github.com/dsccommunity/SqlServerDsc/issues/747)). +- `Get-DscProperty` + - Added parameter `ExcludeName` to exclude property names from being returned. ### Changed @@ -307,6 +309,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and correct localized string ID for each string. - `Set-SqlDscDatabasePermission` - Minor code cleanup. +- `ConvertTo-Reason` + - Fix to handle `$null` values on Windows PowerShell. ## [15.2.0] - 2021-09-01 diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 index b77ffa9dd..481842c84 100644 --- a/source/Private/Get-DscProperty.ps1 +++ b/source/Private/Get-DscProperty.ps1 @@ -61,6 +61,10 @@ function Get-DscProperty [System.String[]] $Name, + [Parameter()] + [System.String[]] + $ExcludeName, + [Parameter()] [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] [System.String[]] @@ -79,6 +83,12 @@ function Get-DscProperty #> (-not $Name -or $_ -in $Name) -and + <# + Return all properties if $ExcludeName is not assigned. Skip + property if it is included in $ExcludeName. + #> + (-not $ExcludeName -or ($_ -notin $ExcludeName)) -and + # Only return the property if it is a DSC property. $InputObject.GetType().GetMember($_).CustomAttributes.Where( { diff --git a/source/Public/New-SqlDscAudit.ps1 b/source/Public/New-SqlDscAudit.ps1 index 0f0de4f9c..1effcb200 100644 --- a/source/Public/New-SqlDscAudit.ps1 +++ b/source/Public/New-SqlDscAudit.ps1 @@ -53,7 +53,8 @@ Specifies the maximum file size in units by parameter MaximumFileSizeUnit. .PARAMETER MaximumFileSizeUnit - Specifies the unit that is used for the file size. this can be KB, MB or GB. + Specifies the unit that is used for the file size. This can be set to `Megabyte`, + `Gigabyte`, or `Terabyte`. .PARAMETER MaximumRolloverFiles Specifies the amount of files on disk before SQL Server starts reusing @@ -244,7 +245,7 @@ function New-SqlDscAudit if ($PSCmdlet.ParameterSetName -match 'FileWithSize') { - $queryMaximumFileSizeUnit = ( + $convertedMaximumFileSizeUnit = ( @{ Megabyte = 'MB' Gigabyte = 'GB' @@ -253,7 +254,7 @@ function New-SqlDscAudit ).$MaximumFileSizeUnit $auditObject.MaximumFileSize = $MaximumFileSize - $auditObject.MaximumFileSizeUnit = $queryMaximumFileSizeUnit + $auditObject.MaximumFileSizeUnit = $convertedMaximumFileSizeUnit } if ($PSCmdlet.ParameterSetName -in @('FileWithMaxFiles', 'FileWithSizeAndMaxFiles')) diff --git a/tests/Unit/Private/Get-DscProperty.Tests.ps1 b/tests/Unit/Private/Get-DscProperty.Tests.ps1 index f4bd4539e..9ac1295ae 100644 --- a/tests/Unit/Private/Get-DscProperty.Tests.ps1 +++ b/tests/Unit/Private/Get-DscProperty.Tests.ps1 @@ -752,4 +752,66 @@ $script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' } } } + + Context 'When excluding specific property names' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty(Mandatory)] +[System.String] +$MyResourceMandatoryProperty + +[DscProperty()] +[System.String] +$MyResourceProperty1 + +[DscProperty()] +[System.String] +$MyResourceProperty2 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty1 = 'MockValue5' +$script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue6' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -ExcludeName @('MyResourceKeyProperty1', 'MyResourceProperty1') -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'the property was excluded' + $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the property was excluded' + + $result.Keys | Should -Contain 'MyResourceKeyProperty2' -Because 'the property has a non-null value and was not excluded' + $result.Keys | Should -Contain 'MyResourceProperty2' -Because 'the property has a non-null value and was not excluded' + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' -Because 'the property has a non-null value and was not excluded' + } + } + } } From 617f1eb697271a823fc8f9db8b904ea26676faf5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 15:28:44 +0200 Subject: [PATCH 28/71] Fix Remove-SqlDscAudit --- source/Public/Remove-SqlDscAudit.ps1 | 2 +- .../Unit/Public/Remove-SqlDscAudit.Tests.ps1 | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/source/Public/Remove-SqlDscAudit.ps1 b/source/Public/Remove-SqlDscAudit.ps1 index 0b51fbc8e..0500dc435 100644 --- a/source/Public/Remove-SqlDscAudit.ps1 +++ b/source/Public/Remove-SqlDscAudit.ps1 @@ -93,6 +93,6 @@ function Remove-SqlDscAudit If the passed audit object has already been dropped, then we silently do nothing, using the method DropIfExist(), since the job is done. #> - $AuditObject.DropIfExist() + $AuditObject.DropIfExists() } } diff --git a/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 index 3c977db46..127027636 100644 --- a/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/Remove-SqlDscAudit.Tests.ps1 @@ -85,8 +85,8 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { $mockServerObject, 'Log1' ) | - Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExist' -Value { - $script:mockMethodDropIfExistCallCount += 1 + Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExists' -Value { + $script:mockMethodDropIfExistsCallCount += 1 } -PassThru -Force } @@ -97,14 +97,14 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { } BeforeEach { - $script:mockMethodDropIfExistCallCount = 0 + $script:mockMethodDropIfExistsCallCount = 0 } Context 'When using parameter Confirm with value $false' { It 'Should call the mocked method and have correct values in the object' { Remove-SqlDscAudit -Confirm:$false @mockDefaultParameters - $mockMethodDropIfExistCallCount | Should -Be 1 + $mockMethodDropIfExistsCallCount | Should -Be 1 } } @@ -112,7 +112,7 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { It 'Should call the mocked method and have correct values in the object' { Remove-SqlDscAudit -Force @mockDefaultParameters - $mockMethodDropIfExistCallCount | Should -Be 1 + $mockMethodDropIfExistsCallCount | Should -Be 1 } } @@ -120,7 +120,7 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { It 'Should call the mocked method and have correct values in the object' { Remove-SqlDscAudit -WhatIf @mockDefaultParameters - $mockMethodDropIfExistCallCount | Should -Be 0 + $mockMethodDropIfExistsCallCount | Should -Be 0 } } @@ -128,7 +128,7 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { It 'Should call the mocked method and have correct values in the object' { $mockServerObject | Remove-SqlDscAudit -Name 'Log1' -Force - $mockMethodDropIfExistCallCount | Should -Be 1 + $mockMethodDropIfExistsCallCount | Should -Be 1 } } } @@ -142,8 +142,8 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { $mockServerObject, 'Log1' ) | - Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExist' -Value { - $script:mockMethodDropIfExistCallCount += 1 + Add-Member -MemberType 'ScriptMethod' -Name 'DropIfExists' -Value { + $script:mockMethodDropIfExistsCallCount += 1 } -PassThru -Force $mockDefaultParameters = @{ @@ -152,14 +152,14 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { } BeforeEach { - $script:mockMethodDropIfExistCallCount = 0 + $script:mockMethodDropIfExistsCallCount = 0 } Context 'When using parameter Confirm with value $false' { It 'Should call the mocked method and have correct values in the object' { Remove-SqlDscAudit -Confirm:$false @mockDefaultParameters - $mockMethodDropIfExistCallCount | Should -Be 1 + $mockMethodDropIfExistsCallCount | Should -Be 1 } } @@ -167,7 +167,7 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { It 'Should call the mocked method and have correct values in the object' { Remove-SqlDscAudit -Force @mockDefaultParameters - $mockMethodDropIfExistCallCount | Should -Be 1 + $mockMethodDropIfExistsCallCount | Should -Be 1 } } @@ -175,7 +175,7 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { It 'Should call the mocked method and have correct values in the object' { Remove-SqlDscAudit -WhatIf @mockDefaultParameters - $mockMethodDropIfExistCallCount | Should -Be 0 + $mockMethodDropIfExistsCallCount | Should -Be 0 } } @@ -183,7 +183,7 @@ Describe 'Remove-SqlDscAudit' -Tag 'Public' { It 'Should call the mocked method and have correct values in the object' { $mockAuditObject | Remove-SqlDscAudit -Force - $mockMethodDropIfExistCallCount | Should -Be 1 + $mockMethodDropIfExistsCallCount | Should -Be 1 } } } From 829af3ee9f306facd5d389d83eeed2429a9c7ab5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 15:29:07 +0200 Subject: [PATCH 29/71] Fix ConvertTo-Reason --- source/Private/ConvertTo-Reason.ps1 | 18 ++++++ tests/Unit/Private/ConvertTo-Reason.Tests.ps1 | 64 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 index fe5c2e0cb..eb5099206 100644 --- a/source/Private/ConvertTo-Reason.ps1 +++ b/source/Private/ConvertTo-Reason.ps1 @@ -71,6 +71,24 @@ function ConvertTo-Reason $propertyActualValue = $currentProperty.ActualValue } + <# + In PowerShell 7 the command ConvertTo-Json returns 'null' on null + value, but not in Windows PowerShell. Switch to output empty string + if value is null. + #> + if ($PSVersionTable.PSEdition -eq 'Desktop') + { + if ($null -eq $propertyExpectedValue) + { + $propertyExpectedValue = '' + } + + if ($null -eq $propertyActualValue) + { + $propertyActualValue = '' + } + } + $reasons += [Reason] @{ Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property # Convert the object to JSON to handle complex types. diff --git a/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 index 7926d5451..89d8e6b4f 100644 --- a/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 +++ b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 @@ -124,4 +124,68 @@ Describe 'ConvertTo-Reason' -Tag 'Private' { } } } + + Context 'When ExpectedValue has $null for a property' { + Context 'When on Windows PowerShell' { + BeforeAll { + $script:originalPSEdition = $PSVersionTable.PSEdition + + $PSVersionTable.PSEdition = 'Desktop' + } + + AfterAll { + $PSVersionTable.PSEdition = $script:originalPSEdition + } + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = $null + ActualValue = 'MyValue1' + } + ) + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 1 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty1' + $result.Phrase | Should -Contain 'The property MyResourceProperty1 should be "", but was "MyValue1"' + } + } + } + } + + Context 'When ActualValue has $null for a property' { + Context 'When on Windows PowerShell' { + BeforeAll { + $script:originalPSEdition = $PSVersionTable.PSEdition + + $PSVersionTable.PSEdition = 'Desktop' + } + + AfterAll { + $PSVersionTable.PSEdition = $script:originalPSEdition + } + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyValue1' + ActualValue = $null + } + ) + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 1 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty1' + $result.Phrase | Should -Contain 'The property MyResourceProperty1 should be "MyValue1", but was ""' + } + } + } + } } From be7d1bc85a77a86cce8bbed29eaeec5f95b05e83 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 15:39:28 +0200 Subject: [PATCH 30/71] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d29d410c..fffaf128e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -329,11 +329,11 @@ if (-not $databaseExist) A terminating error is an error that the user are not able to ignore by passing a parameter to the command (like for non-terminating errors). -If a command shall throw an terminating error the statement `throw` shall -not be used, neither shall the command `Write-Error` be used with the parameter -`-ErrorAction `Stop``. Instead the method `$PSCmdlet.ThrowTerminatingError()` -shall be used to throw a terminating error. - +If a command shall throw an terminating error then the statement `throw` shall +not be used, neither shall the command `Write-Error` with the parameter +`-ErrorAction Stop`. Always use the method `$PSCmdlet.ThrowTerminatingError()` +to throw a terminating error. The exception is when a `[ValidateScript()]` +has to throw an error, then `throw` must be used. >**NOTE:** Below output assumes `$ErrorView` is set to `'NormalView'` in the >PowerShell session. From 1ea6d6b3375c8ee074313e426efebef292d8f7fa Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 15:39:46 +0200 Subject: [PATCH 31/71] Fix Set-SqlDscAudit --- source/Public/Set-SqlDscAudit.ps1 | 20 ++++++++++-- tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 | 34 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/source/Public/Set-SqlDscAudit.ps1 b/source/Public/Set-SqlDscAudit.ps1 index 6087579ad..ff3695a52 100644 --- a/source/Public/Set-SqlDscAudit.ps1 +++ b/source/Public/Set-SqlDscAudit.ps1 @@ -49,6 +49,8 @@ .PARAMETER MaximumFileSize Specifies the maximum file size in units by parameter MaximumFileSizeUnit. + Minimum allowed value is 2 (MB). It also allowed to set the value to 0 which + mean unlimited file size. .PARAMETER MaximumFileSizeUnit Specifies the unit that is used for the file size. this can be KB, MB or GB. @@ -133,7 +135,14 @@ function Set-SqlDscAudit $OnFailure, [Parameter()] - [ValidateRange(1000, 2147483647)] + [ValidateScript({ + if ($_ -in 1..999 -or $_ -gt 2147483647) + { + throw ($script:localizedData.Audit_QueueDelayParameterValueInvalid -f $_) + } + + return $true + })] [System.UInt32] $QueueDelay, @@ -183,7 +192,14 @@ function Set-SqlDscAudit [Parameter(ParameterSetName = 'AuditObjectWithSize', Mandatory = $true)] [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxFiles', Mandatory = $true)] [Parameter(ParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles', Mandatory = $true)] - [ValidateRange(2, 2147483647)] + [ValidateScript({ + if ($_ -eq 1 -or $_ -gt 2147483647) + { + throw ($script:localizedData.Audit_MaximumFileSizeParameterValueInvalid -f $_) + } + + return $true + })] [System.UInt32] $MaximumFileSize, diff --git a/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 index cdf3a3ff6..eef5f1098 100644 --- a/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 @@ -292,6 +292,40 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { } } + Context 'When adding an file audit and passing an invalid MaximumFileSize' { + It 'Should throw the correct error when the value is <_>' -ForEach @(1, 2147483648) { + $mockNewSqlDscAuditParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Name = 'Log1' + MaximumFileSize = $_ + } + + $mockErrorMessage = 'Cannot validate argument on parameter ''MaximumFileSize''. ' + $mockErrorMessage += InModuleScope -ScriptBlock { + $script:localizedData.Audit_MaximumFileSizeParameterValueInvalid + } + + { Set-SqlDscAudit @mockNewSqlDscAuditParameters } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + Context 'When adding an file audit and passing an invalid QueueDelay' { + It 'Should throw the correct error when the value is <_>' -ForEach @(1, 457, 999, 2147483648) { + $mockNewSqlDscAuditParameters = @{ + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Name = 'Log1' + QueueDelay = $_ + } + + $mockErrorMessage = 'Cannot validate argument on parameter ''QueueDelay''. ' + $mockErrorMessage += InModuleScope -ScriptBlock { + $script:localizedData.Audit_QueueDelayParameterValueInvalid + } + + { Set-SqlDscAudit @mockNewSqlDscAuditParameters } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + Context 'When passing file audit optional parameter Path' { BeforeAll { $script:mockAuditObject = $null From ec6442096872a17d32ef655d3ef175564224a79f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 15:58:51 +0200 Subject: [PATCH 32/71] Fix Ensure in ResourceBase --- CHANGELOG.md | 6 ++ source/Classes/010.ResourceBase.ps1 | 77 ++++++----------------- tests/Unit/Classes/ResourceBase.Tests.ps1 | 49 ++++++++++----- 3 files changed, 57 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da34ac4e..d9041638c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -311,6 +311,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Minor code cleanup. - `ConvertTo-Reason` - Fix to handle `$null` values on Windows PowerShell. +- `ResourceBase` + - Now handles `Ensure` correctly from derived `GetCurrentState()`. But + requires that the `GetCurrentState()` only return key property if object + is present, and does not return key property if object is absent. + Optionally the resource's derived `GetCurrentState()` can handle `Ensure` + itself. ## [15.2.0] - 2021-09-01 diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 index b0a730ccf..642bd7b56 100644 --- a/source/Classes/010.ResourceBase.ps1 +++ b/source/Classes/010.ResourceBase.ps1 @@ -52,6 +52,8 @@ class ResourceBase } } + $keyPropertyAddedToCurrentState = $false + # Set key property values unless it was returned from the derived class' GetCurrentState(). foreach ($propertyName in $keyProperty.Keys) { @@ -59,79 +61,36 @@ class ResourceBase { # Add the key value to the instance to be returned. $dscResourceObject.$propertyName = $this.$propertyName - } - } - - $ignoreProperty = @() - <# - TODO: This need to be re-evaluated for a resource that is using Ensure - property. How Ensure is handled might need to be refactored, or - removed altogether from this base class. - - If the derived DSC resource has a Ensure property and it was not returned - by GetCurrentState(), then the property Ensure is removed from the - comparison (when calling Compare()). The property Ensure is ignored - since the method GetCurrentState() did not return it, and the current - state for property Ensure cannot be determined until the method Compare() - has run to determined if other properties are not in desired state. - #> - if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) - { - $ignoreProperty += 'Ensure' + $keyPropertyAddedToCurrentState = $true + } } - <# - Returns all enforced properties not in desires state, or $null if - all enforced properties are in desired state. - #> - $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, $ignoreProperty) - - <# - Return the correct values for Ensure property if the derived DSC resource - has such property and it hasn't been already set by GetCurrentState(). - #> if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) { - if ($propertiesNotInDesiredState) + # Evaluate if we should set Ensure property. + if ($keyPropertyAddedToCurrentState) { <# - Get all the key properties that might not be in desired state. - This will return $null if all key properties are in desired state. + A key property was added to the current state, assume its because + the object did not exist in the current state. Set Ensure to Absent. #> - $keyPropertiesNotInDesiredState = $this | Get-DscProperty -Name $propertiesNotInDesiredState.Property -Type 'Key' - - if ($keyPropertiesNotInDesiredState) - { - <# - The compare come back with at least one key property that was - not in desired state. That only happens if the object does not - exist on the node, so the Ensure value is set to Absent since - the object does not exist. - #> - $dscResourceObject.Ensure = [Ensure]::Absent - } - else - { - <# - The compare come back with all key properties in desired state. - That only happens if the object exist on the node, so the Ensure - value is set to Present since the object exist. - #> - $dscResourceObject.Ensure = [Ensure]::Present - } + $dscResourceObject.Ensure = [Ensure]::Absent + $getCurrentStateResult.Ensure = [Ensure]::Absent } else { - <# - The compare come back with $null, meaning that all key properties - match. That only happens if the object exist on the node, so the - Ensure value is set to Present since the object exist. - #> $dscResourceObject.Ensure = [Ensure]::Present + $getCurrentStateResult.Ensure = [Ensure]::Present } } + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, @()) + <# Return the correct values for Reasons property if the derived DSC resource has such property and it hasn't been already set by GetCurrentState(). @@ -140,7 +99,7 @@ class ResourceBase { # Always return an empty array if all properties are in desired state. $dscResourceObject.Reasons = $propertiesNotInDesiredState | - ConvertTo-Reason -ResourceName $this.GetType() + ConvertTo-Reason -ResourceName $this.GetType().Name } # Return properties. diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 index 9a3379816..bbc6f5c18 100644 --- a/tests/Unit/Classes/ResourceBase.Tests.ps1 +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -101,7 +101,7 @@ Describe 'ResourceBase\Get()' -Tag 'Get' { } } - Context 'When the configuration should be present' { + Context 'When the object should be Present' { BeforeAll { Mock -CommandName Get-ClassName -MockWith { # Only return localized strings for this class name. @@ -153,6 +153,7 @@ class MyMockResource : ResourceBase the base class' method Get() return that value. #> return @{ + MyResourceKeyProperty1 = 'MyValue1' MyResourceProperty2 = 'MyValue2' } } @@ -186,7 +187,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() } } - Context 'When the configuration should be absent' { + Context 'When the object should be Absent' { BeforeAll { Mock -CommandName Get-ClassName -MockWith { # Only return localized strings for this class name. @@ -226,13 +227,12 @@ class MyMockResource : ResourceBase MyMockResource() : base () { # Test not to add the key property to the list of properties that are not enforced. - $this.notEnforcedProperties = @() + $this.notEnforcedProperties = @('MyResourceKeyProperty1') } [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { return @{ - MyResourceKeyProperty1 = $null MyResourceProperty2 = $null } } @@ -258,12 +258,10 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult = $mockResourceBaseInstance.Get() - $getResult.MyResourceKeyProperty1 | Should -BeNullOrEmpty + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' $getResult.MyResourceProperty2 | Should -BeNullOrEmpty $getResult.Ensure | Should -Be ([Ensure]::Absent) - - $getResult.Reasons | Should -HaveCount 1 - $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceKeyProperty1' + $getResult.Reasons | Should -BeNullOrEmpty } } } @@ -450,6 +448,12 @@ class MyMockResource : ResourceBase [Reason[]] $Reasons + MyMockResource() : base () + { + # Test not to add the key property to the list of properties that are not enforced. + $this.notEnforcedProperties = @('MyResourceKeyProperty1') + } + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { return @{ @@ -481,7 +485,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' $getResult.MyResourceProperty2 | Should -Be 'MyValue2' - $getResult.Ensure | Should -Be ([Ensure]::Absent) + $getResult.Ensure | Should -Be ([Ensure]::Present) $getResult.Reasons | Should -HaveCount 1 $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' @@ -490,7 +494,7 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() } } - Context 'When a mandatory parameter is not in desired state' { + Context 'When the object should be Present' { BeforeAll { <# Must use a here-string because we need to pass 'using' which must be @@ -518,10 +522,15 @@ class MyMockResource : ResourceBase [Reason[]] $Reasons + MyMockResource() : base () + { + # Test not to add the key property to the list of properties that are not enforced. + $this.notEnforcedProperties = @('MyResourceKeyProperty1') + } + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { return @{ - MyResourceKeyProperty1 = $null } } } @@ -545,18 +554,18 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult = $mockResourceBaseInstance.Get() - $getResult.MyResourceKeyProperty1 | Should -BeNullOrEmpty + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' $getResult.Ensure | Should -Be ([Ensure]::Absent) $getResult.Reasons | Should -HaveCount 1 - $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceKeyProperty1' - $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceKeyProperty1 should be "MyValue1", but was null' + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:Ensure' + $getResult.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' } } } } - Context 'When the configuration should be absent' { + Context 'When the object should be Absent' { BeforeAll { Mock -CommandName Get-ClassName -MockWith { # Only return localized strings for this class name. @@ -589,6 +598,12 @@ class MyMockResource : ResourceBase [Reason[]] $Reasons + MyMockResource() : base () + { + # Test not to add the key property to the list of properties that are not enforced. + $this.notEnforcedProperties = @('MyResourceKeyProperty1') + } + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { return @{ @@ -622,7 +637,9 @@ $script:mockResourceBaseInstance = [MyMockResource]::new() $getResult.MyResourceProperty2 | Should -Be 'MyValue2' $getResult.Ensure | Should -Be ([Ensure]::Present) - $getResult.Reasons | Should -BeNullOrEmpty + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:Ensure' + $getResult.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Absent", but was "Present"' } } } From 8c48ebac090e3cdaed4036b606db0687a4f22940 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 16:00:07 +0200 Subject: [PATCH 33/71] Fix comment-based help --- CHANGELOG.md | 3 +++ source/Classes/020.SqlDatabasePermission.ps1 | 4 ++-- source/Classes/020.SqlPermission.ps1 | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9041638c..55862515a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -307,6 +307,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed comment-based help and cleaned up comments. - Fix localized string that referenced 'user' instead of 'principal', and correct localized string ID for each string. + - Fix comment-based help. +- SqlPermission + - Fix comment-based help. - `Set-SqlDscDatabasePermission` - Minor code cleanup. - `ConvertTo-Reason` diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 index e89f97705..fa94dfbfa 100644 --- a/source/Classes/020.SqlDatabasePermission.ps1 +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -24,8 +24,8 @@ ### `PSDscRunAsCredential` not supported The built-in property `PSDscRunAsCredential` does not work with class-based - resources that using advanced type like the parameter `Permission` does. - Use the parameter `Credential` instead of `PSDscRunAsCredential`. + resources that using advanced type like the parameters `Permission` and + `Reasons` has. Use the parameter `Credential` instead of `PSDscRunAsCredential`. ### Using `Credential` property. diff --git a/source/Classes/020.SqlPermission.ps1 b/source/Classes/020.SqlPermission.ps1 index cb69a7626..696b7fcbe 100644 --- a/source/Classes/020.SqlPermission.ps1 +++ b/source/Classes/020.SqlPermission.ps1 @@ -26,8 +26,8 @@ ### `PSDscRunAsCredential` not supported The built-in property `PSDscRunAsCredential` does not work with class-based - resources that using advanced type like the parameter `Permission` does. - Use the parameter `Credential` instead of `PSDscRunAsCredential`. + resources that using advanced type like the parameters `Permission` and + `Reasons` has. Use the parameter `Credential` instead of `PSDscRunAsCredential`. ### Using `Credential` property. From 014559fd9621042c37adf64ee5a76ae326350ffc Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 16:00:39 +0200 Subject: [PATCH 34/71] Fix Set-SqlDscAudit localized strings --- source/en-US/SqlServerDsc.strings.psd1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 22675159e..45ae2660b 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -54,6 +54,8 @@ ConvertFrom-StringData @' Audit_Update_ShouldProcessVerboseWarning = Are you sure you want you update the audit '{0}'? # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Audit_Update_ShouldProcessCaption = Update audit on instance + Audit_MaximumFileSizeParameterValueInvalid = The maximum file size must be set to a value of 0 or a value between 2 and 2147483647. + Audit_QueueDelayParameterValueInvalid = The queue delay must be set to a value of 0 or a value between 1000 and 2147483647. ## Get-SqlDscAudit Audit_Missing = There is no audit with the name '{0}'. From 4172c57ca58a9a17ed2d13c0f2f0c97fdc6572fc Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 16:04:11 +0200 Subject: [PATCH 35/71] Remove MOF-based resource SqlServerAudit --- .../DSC_SqlServerAudit.psm1 | 919 ------------------ .../DSC_SqlServerAudit.schema.mof | 20 - .../en-US/DSC_SqlServerAudit.strings.psd1 | 19 - tests/Unit/DSC_SqlServerAudit.Tests.ps1 | 816 ---------------- 4 files changed, 1774 deletions(-) delete mode 100644 source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 delete mode 100644 source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof delete mode 100644 source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 delete mode 100644 tests/Unit/DSC_SqlServerAudit.Tests.ps1 diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 deleted file mode 100644 index 5805dce8d..000000000 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.psm1 +++ /dev/null @@ -1,919 +0,0 @@ -$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 - Returns the current state of the audit on a server. - - .PARAMETER Name - Specifies the name of the server audit to be added or removed. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exists. - - .PARAMETER InstanceName - Specifies the SQL instance in which the audit exists. - - .PARAMETER DestinationType - Specifies the location where the audit should write to. - This can be File, SecurityLog or ApplicationLog. - - Not used in Get-TargetResource. -#> -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter()] - [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] - [System.String] - $DestinationType - ) - - $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - - Write-Verbose -Message ( - $script:localizedData.RetrievingAuditInfo -f $Name, $ServerName - ) - - $returnValue = @{ - Ensure = 'Absent' - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - DestinationType = $null - FilePath = $null - Filter = $null - MaximumFiles = $null - MaximumFileSize = $null - MaximumFileSizeUnit = $null - MaximumRolloverFiles = $null - OnFailure = $null - QueueDelay = $null - ReserveDiskSpace = $null - Enabled = $false - } - - # Check if database exists. - $sqlServerAuditObject = $sqlServerObject.Audits[$Name] - - if ($sqlServerAuditObject) - { - Write-Verbose -Message ( - $script:localizedData.AuditExist -f $Name, $ServerName - ) - - $returnValue['Ensure'] = 'Present' - $returnValue['DestinationType'] = $sqlServerAuditObject.DestinationType - $returnValue['FilePath'] = $sqlServerAuditObject.FilePath - $returnValue['MaximumFiles'] = $sqlServerAuditObject.MaximumFiles - $returnValue['MaximumFileSize'] = $sqlServerAuditObject.MaximumFileSize - $returnValue['MaximumFileSizeUnit'] = $sqlServerAuditObject.MaximumFileSizeUnit - $returnValue['MaximumRolloverFiles'] = $sqlServerAuditObject.MaximumRolloverFiles - $returnValue['ReserveDiskSpace'] = $sqlServerAuditObject.ReserveDiskSpace - $returnValue['Filter'] = $sqlServerAuditObject.Filter - $returnValue['OnFailure'] = $sqlServerAuditObject.OnFailure - $returnValue['QueueDelay'] = $sqlServerAuditObject.QueueDelay - $returnValue['Enabled'] = $sqlServerAuditObject.Enabled - } - - return $returnValue -} - -<# - .SYNOPSIS - sets the server audit in desired state. - - .PARAMETER Name - Specifies the name of the audit to be tested. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance where the audit should be tested. - - .PARAMETER DestinationType - Specifies the location where the audit should write to. - This can be File, SecurityLog or ApplicationLog. - - .PARAMETER FilePath - Specifies the location where te log files wil be placed. - - .PARAMETER Filter - Specifies the filter that should be used on the audit. - - .PARAMETER MaximumFiles - Specifies the number of files on disk. - - .PARAMETER MaximumFileSize - Specifies the maximum file size in units by parameter MaximumFileSizeUnit. - - .PARAMETER MaximumFileSizeUnit - Specifies the unit that is used for the file size. this can be KB, MB or GB. - - .PARAMETER MaximumRolloverFiles - Specifies the amount of files on disk before SQL Server starts reusing - the files. - - .PARAMETER OnFailure - Specifies what should happen when writing events to the store fails. - This can be CONTINUE, FAIL_OPERATION or SHUTDOWN. - - .PARAMETER QueueDelay - Specifies the maximum delay before a event is written to the store. - When set to low this could impact server performance. - When set to high events could be missing when a server crashes. - - .PARAMETER ReserveDiskSpace - Specifies if the needed file space should be reserved. only needed - when writing to a file log. - - .PARAMETER Enabled - Specifies if the audit should be enabled. Defaults to $false. - - .PARAMETER Ensure - Specifies if the server audit should be present or absent. If 'Present' - then the audit will be added to the server and, if needed, the audit - will be updated. If 'Absent' then the audit will be removed from - the server. Defaults to 'Present'. - - .PARAMETER Force - Specifies if it is allowed to re-create the server audit when the DestinationType - changes. Defaults to $false not allowing server audits to be re-created. -#> -function Set-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'The command Invoke-Query is used which calls the command Connect-Sql')] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter()] - [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] - [System.String] - $DestinationType, - - [Parameter()] - [System.String] - $FilePath, - - [Parameter()] - [System.String] - $Filter, - - [Parameter()] - [System.UInt32] - $MaximumFiles, - - [Parameter()] - [System.UInt32] - $MaximumFileSize = 10, - - [Parameter()] - [ValidateSet('KB', 'MB', 'GB')] - [System.String] - $MaximumFileSizeUnit = 'MB', - - [Parameter()] - [System.UInt32] - $MaximumRolloverFiles, - - [Parameter()] - [ValidateSet('CONTINUE', 'FAIL_OPERATION', 'SHUTDOWN')] - [System.String] - $OnFailure = 'CONTINUE', - - [Parameter()] - [System.UInt32] - $QueueDelay = 1000, - - [Parameter()] - [System.Boolean] - $ReserveDiskSpace = $false, - - [Parameter()] - [System.Boolean] - $Enabled = $false, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter()] - [System.Boolean] - $Force - ) - - Write-Verbose -Message ( - $script:localizedData.SetAudit -f $Name, $ServerName, $InstanceName - ) - - #sanitize user input. - if (($MaximumFiles) -and ($MaximumRolloverFiles)) - { - $errorMessage = $script:localizedData.ImpossibleFileCombination - New-InvalidOperationException -Message $errorMessage - } - - if ($FilePath) - { - $FilePath = $FilePath.TrimEnd('\') + '\' - - #Test if audit file location exists, and create if it does not. - if (-not (Test-Path -Path $FilePath)) - { - Write-Verbose -Message ( - $script:localizedData.CreateFolder -f $FilePath.TrimEnd('\') - ) - New-Item -ItemType directory -Path $FilePath.TrimEnd('\') - } - } - - #parameters for the TargetResource cmdlet. - $getTargetResourceParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Name = $Name - } - - # Get-TargetResource will also help us to test if the audit exist. - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - # Default parameters for the cmdlet Invoke-Query used throughout. - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - $recreateAudit = $false - - if ($getTargetResourceResult.Ensure -eq $Ensure) - { - if ($Ensure -eq 'Present') - { - # Update Audit properties, if needed drop and recreate. - if ($DestinationType -eq $getTargetResourceResult.DestinationType) - { - switch ($DestinationType) - { - 'File' - { - $strReserveDiskSpace = 'OFF' - if ($ReserveDiskSpace) - { - $strReserveDiskSpace = 'ON' - } - - $strFiles = '' - if ($MaximumFiles) - { - $strFiles = 'MAX_FILES = {0},' -f $MaximumFiles - } - if ($MaximumRolloverFiles) - { - $strFiles = 'MAX_ROLLOVER_FILES = {0},' -f $MaximumRolloverFiles - } - - $target = 'FILE ( - FILEPATH = N''{0}'', - MAXSIZE = {1} {2}, - {3} - RESERVE_DISK_SPACE = {4} )' -f - $FilePath, - $MaximumFileSize, - $MaximumFileSizeUnit, - $strFiles, - $strReserveDiskSpace - } - - 'SecurityLog' - { - $target = 'SECURITY_LOG' - } - - 'ApplicationLog' - { - $target = 'APPLICATION_LOG' - } - } - - $withPart = 'QUEUE_DELAY = {0}, - ON_FAILURE = {1}' -f - $QueueDelay, - $OnFailure - - $ServerAuditParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Name = $Name - Action = 'ALTER' - Target = $target - WithPart = $withPart - } - - # If current audit state is enabled, disable it before edit. - if ($getTargetResourceResult.Enabled -eq $true) - { - Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName - } - - try - { - Set-ServerAudit @ServerAuditParameters - } - catch - { - $errorMessage = $script:localizedData.FailedUpdateAudit -f $Name, $ServerName, $InstanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - - # If audit state was disabled for edit, Re-enable it. - if ($getTargetResourceResult.Enabled -eq $true) - { - Enable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName - } - } - else - { - <# - Current server audit has a different storage type, the - server audit needs to be re-created. - #> - Write-Verbose -Message ( - $script:localizedData.ChangingAuditDestinationType -f - $Name, - $getTargetResourceResult.DestinationType, - $DestinationType, - $ServerName, - $InstanceName - ) - - $recreateAudit = $true - } - } - } - - # Throw if not opt-in to re-create database user. - if ($recreateAudit -and -not $Force) - { - $errorMessage = $script:localizedData.ForceNotEnabled - New-InvalidOperationException -Message $errorMessage - } - - if (($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) - { - # Drop the server audit. - try - { - Write-Verbose -Message ( - $script:localizedData.DropAudit -f $Name, $serverName, $instanceName - ) - - #if current audit state is enabled, disable it before removal. - if ($getTargetResourceResult.Enabled -eq $true) - { - Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName - } - - Invoke-Query @invokeQueryParameters -Query ( - 'DROP SERVER AUDIT [{0}];' -f $Name - ) - } - catch - { - $errorMessage = $script:localizedData.FailedDropAudit -f $Name, $ServerName, $InstanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - - <# - This evaluation is made to handle creation and re-creation of a server - audit to minimize the logic when the user has a different storage type, or - when there are restrictions on altering an existing audit. - #> - if (($Ensure -eq 'Present' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) - { - # Create the audit. - Write-Verbose -Message ( - $script:localizedData.CreateAudit -f $Name, $ServerName, $InstanceName - ) - - switch ($DestinationType) - { - 'File' - { - $strReserveDiskSpace = 'OFF' - if ($ReserveDiskSpace) - { - $strReserveDiskSpace = 'ON' - } - - $strFiles = '' - if ($MaximumFiles) - { - $strFiles = 'MAX_FILES = {0},' -f $MaximumFiles - } - if ($MaximumRolloverFiles) - { - $strFiles = 'MAX_ROLLOVER_FILES = {0},' -f $MaximumRolloverFiles - } - - $target = 'FILE ( - FILEPATH = N''{0}'', - MAXSIZE = {1} {2}, - {3} - RESERVE_DISK_SPACE = {4} )' -f - $FilePath, - $MaximumFileSize, - $MaximumFileSizeUnit, - $strFiles, - $strReserveDiskSpace - } - - 'SecurityLog' - { - $target = 'SECURITY_LOG' - } - - 'ApplicationLog' - { - $target = 'APPLICATION_LOG' - } - } - - $withPart = 'QUEUE_DELAY = {0}, - ON_FAILURE = {1}' -f - $QueueDelay, - $OnFailure - - $ServerAuditParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Name = $Name - Action = 'CREATE' - Target = $target - WithPart = $withPart - } - - try - { - Set-ServerAudit @ServerAuditParameters - } - catch - { - $errorMessage = $script:localizedData.FailedCreateAudit -f $Name, $ServerName, $InstanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - - if ($Ensure -eq 'Present' -and $getTargetResourceResult.Filter -ne $Filter) - { - try - { - Write-Verbose -Message ( - $script:localizedData.AddFilter -f $Filter, $Name, $serverName, $instanceName - ) - - #if current audit state is enabled, disable it before setting filter. - if ($getTargetResourceResult.Enabled -eq $true) - { - Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName - } - - if ($null -ne $Filter -and $Filter -ne '') - { - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT [{0}] WHERE {1};' -f $Name, $Filter - ) - } - else - { - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT [{0}] REMOVE WHERE;' -f $Name - ) - } - } - catch - { - $errorMessage = $script:localizedData.FailedAddFilter -f $Filter, $Name, $ServerName, $InstanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - - if ($Ensure -eq 'Present' -and $getTargetResourceResult.Enabled -ne $Enabled) - { - if ($Enabled -eq $true) - { - Enable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName - } - else - { - Disable-Audit -Name $Name -ServerName $ServerName -InstanceName $InstanceName - } - } -} - -<# - .SYNOPSIS - Determines if the server audit is in desired state. - - .PARAMETER Name - Specifies the name of the audit to be tested. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance where the audit should be tested. - - .PARAMETER DestinationType - Specifies the location where the audit should write to. - This can be File, SecurityLog or ApplicationLog. - - .PARAMETER FilePath - Specifies the location where te log files wil be placed. - - .PARAMETER Filter - Specifies the filter that should be used on the audit. - - .PARAMETER MaximumFiles - Specifies the number of files on disk. - - .PARAMETER MaximumFileSize - Specifies the maximum file size in units by parameter MaximumFileSizeUnit. - - .PARAMETER MaximumFileSizeUnit - Specifies the unit that is used for the file size. this can be KB, MB or GB. - - .PARAMETER MaximumRolloverFiles - Specifies the amount of files on disk before SQL Server starts reusing - the files. - - .PARAMETER OnFailure - Specifies what should happen when writing events to the store fails. - This can be CONTINUE, FAIL_OPERATION or SHUTDOWN. - - .PARAMETER QueueDelay - Specifies the maximum delay before a event is written to the store. - When set to low this could impact server performance. - When set to high events could be missing when a server crashes. - - .PARAMETER ReserveDiskSpace - Specifies if the needed file space should be reserved. only needed - when writing to a file log. - - .PARAMETER Enabled - Specifies if the audit should be enabled. Defaults to $false. - - .PARAMETER Ensure - Specifies if the server audit should be present or absent. If 'Present' - then the audit will be added to the server and, if needed, the audit - will be updated. If 'Absent' then the audit will be removed from - the server. Defaults to 'Present'. - - .PARAMETER Force - Specifies if it is allowed to re-create the server audit when the DestinationType - changes. Defaults to $false not allowing server audits to be re-created. -#> -function Test-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'The command Connect-Sql is called when Get-TargetResource is called')] - [CmdletBinding()] - [OutputType([System.Boolean])] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter()] - [ValidateSet('File', 'SecurityLog', 'ApplicationLog')] - [System.String] - $DestinationType, - - [Parameter()] - [System.String] - $FilePath, - - [Parameter()] - [System.String] - $Filter, - - [Parameter()] - [System.UInt32] - $MaximumFiles = 10, - - [Parameter()] - [System.UInt32] - $MaximumFileSize = 10, - - [Parameter()] - [ValidateSet('KB', 'MB', 'GB')] - [System.String] - $MaximumFileSizeUnit = 'MB', - - [Parameter()] - [System.UInt32] - $MaximumRolloverFiles = '10', - - [Parameter()] - [ValidateSet('CONTINUE', 'FAIL_OPERATION', 'SHUTDOWN')] - [System.String] - $OnFailure = 'CONTINUE', - - [Parameter()] - [System.UInt32] - $QueueDelay = 1000, - - [Parameter()] - [System.Boolean] - $ReserveDiskSpace = $false, - - [Parameter()] - [System.Boolean] - $Enabled = $false, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter()] - [System.Boolean] - $Force - ) - - Write-Verbose -Message ( - $script:localizedData.EvaluateAudit -f $Name, $ServerName, $InstanceName - ) - - #sanitize user input. - if ($FilePath) - { - $FilePath = $FilePath.TrimEnd('\') + '\' - $PSBoundParameters['FilePath'] = $FilePath.TrimEnd('\') + '\' - } - - $getTargetResourceParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Name = $Name - } - - # Get-TargetResource will also help us to test if the audit exist. - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - $testTargetResourceReturnValue = $true - - if ($getTargetResourceResult.Ensure -eq $Ensure) - { - if ($Ensure -eq 'Present') - { - $testDscParameterStateParameters = @{ - CurrentValues = $getTargetResourceResult - DesiredValues = $PSBoundParameters - ExcludeProperties = @( - 'Name' - 'ServerName' - 'InstanceName' - 'Force' - # Ensure was already evaluated prior, no need to check it again. - 'Ensure' - ) - } - - $propertiesNotInDesiredState = Compare-DscParameterState @testDscParameterStateParameters - - if ($propertiesNotInDesiredState) - { - $testTargetResourceReturnValue = $false - } - - # <# - # WORKAROUND for possible bug? - # Test-DscParameterState does not see if a parameter is removed as parameter - # but still exists in the DSC resource. - - # When in desired state do some additional tests. - # When not in desired state, additional testing is not needed. - # #> - # if ($testTargetResourceReturnValue) - # { - # if ($getTargetResourceResult.Filter -ne $Filter) - # { - # $testTargetResourceReturnValue = $false - # } - # } - } - } - else - { - $testTargetResourceReturnValue = $false - } - - if ($testTargetResourceReturnValue) - { - Write-Verbose -Message $script:localizedData.InDesiredState - } - else - { - Write-Verbose -Message $script:localizedData.NotInDesiredState - } - - return $testTargetResourceReturnValue -} - -<# - .SYNOPSIS - Disables a server audit. - - .PARAMETER Name - Specifies the name of the server audit to be disabled. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the audit exist. -#> -function Disable-Audit -{ - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name - ) - - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT [{0}] WITH (STATE = OFF);' -f $Name - ) -} - -<# - .SYNOPSIS - Enables a server audit. - - .PARAMETER Name - Specifies the name of the server audit to enabled. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the audit exist. -#> -function Enable-Audit -{ - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name - ) - - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT [{0}] WITH (STATE = ON);' -f $Name - ) -} - -<# - .SYNOPSIS - Creates an server audit. Alters if already exists. - - .PARAMETER Name - Specifies the name of the server audit to be added or removed. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the database exist. - - .PARAMETER Action - Specifies if the audit should be created or altered. - - .PARAMETER Target - Specifies if the target is the securityLog, the applicationLog or a File Log. - When a File log it also should contain al the file information. - - .PARAMETER WithPart - Specifies what should go in the WITH part of the audit query. -#> -function Set-ServerAudit -{ - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [ValidateSet('CREATE', 'ALTER')] - [System.String] - $Action, - - [Parameter()] - [System.String] - $Target, - - [Parameter()] - [System.String] - $WithPart - ) - - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - Invoke-Query @invokeQueryParameters -Query ( - '{0} SERVER AUDIT [{1}] TO {2} - WITH ( - {3} - );' -f - $Action, - $Name, - $Target, - $WithPart - ) -} diff --git a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof b/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof deleted file mode 100644 index 03cf2dcbb..000000000 --- a/source/DSCResources/DSC_SqlServerAudit/DSC_SqlServerAudit.schema.mof +++ /dev/null @@ -1,20 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("SqlServerAudit")] -class DSC_SqlServerAudit : OMI_BaseResource -{ - [Key, Description("Specifies the host name of the SQL Server on which the instance exist.")] String ServerName; - [Key, Description("Specifies the SQL instance in which the Audit exist.")] String InstanceName; - [Key, Description("Specifies the name of the SQL audit to be added or removed.")] String Name; - [Write, Description("Specifies the location where the audit should write to."), ValueMap{"File", "SecurityLog", "ApplicationLog"}, Values{"File", "SecurityLog", "ApplicationLog"}] String DestinationType; - [Write, Description("Specifies the path where the audit files are stored when DestinationType is set to `File`.")] String FilePath; - [Write, Description("Specifies if the audit should be enabled. Defaults to `$false`.")] Boolean Enabled; - [Write, Description("Specifies the filter that should be used on the audit.")] String Filter; - [Write, Description("Specifies the number of file a file audit can have.")] UInt32 MaximumFiles; - [Write, Description("Specifies the maximum file size of an audit file")] UInt32 MaximumFileSize; - [Write, Description("Specifies the FileSize unit of an audit file"), ValueMap{"KB", "MB", "GB"}, Values{"KB", "MB", "GB"}] String MaximumFileSizeUnit; - [Write, Description("Specifies the number of file a file audit can have before a rollover starts")] UInt32 MaximumRolloverFiles; - [Write, Description("Specifies the maximum delay before a event is written to disk. defaults to 1000 ( 1 sec ). When set to low, performance can degrade. When set to high, events can get lost with a server crash.")] UInt32 QueueDelay; - [Write, Description("Specifies if the disk space for the audit files should be reserved. Defaults to $false")] Boolean ReserveDiskSpace; - [Write, Description("Specifies the type of the database user. Valid"), ValueMap{"CONTINUE", "FAIL_OPERATION", "SHUTDOWN"}, Values{"CONTINUE", "FAIL_OPERATION", "SHUTDOWN"}] String OnFailure; - [Write, Description("Specifies if the audit should be present or absent. If 'Present' then the audit will be added to the server and, if needed, the audit will be updated. If 'Absent' then the audit will be removed from the server. Defaults to 'Present'."), ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure; - [Write, Description("Specifies if it is allowed to re-create the server audit when the DestinationType changes. Defaults to $false not allowing server audits to be re-created.")] Boolean Force; -}; diff --git a/source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 b/source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 deleted file mode 100644 index 26e5b4746..000000000 --- a/source/DSCResources/DSC_SqlServerAudit/en-US/DSC_SqlServerAudit.strings.psd1 +++ /dev/null @@ -1,19 +0,0 @@ -ConvertFrom-StringData @' - RetrievingAuditInfo = Retrieving information about Audit '{0}' from the server '{1}'. (SSA0001) - EvaluateAudit = Determining if the audit '{0}' on server '{1}' instance '{2}' is in the desired state. (SSA0002) - AuditExist = The audit '{0}' exist in on server '{1}'. (SSA0003) - InDesiredState = The audit is in desired state. (SSA0004) - NotInDesiredState = The audit is not in desired state. (SSA0005) - CreateAudit = Creating the audit '{0}' on server '{1}' instance '{2}'. (SSA0006) - FailedCreateAudit = Failed creating audit '{0}' on server '{1}' instance '{2}'. (SSA0007) - DropAudit = Removing the audit '{0}' from server '{1}' instance '{2}'. (SSA0008) - FailedDropAudit = Failed removing the audit '{0}' from server '{1}' instance '{2}'. (SSA0009) - SetAudit = Setting the audit '{0}' on server '{1}' instance '{2}' to the desired state. (SSA0010) - FailedUpdateAudit = Failed updating audit '{0}' on server '{1}' instance '{2}'. (SSA0011) - ChangingAuditDestinationType = The audit '{0}' currently has destination type '{1}', but expected it to be '{2}'. Re-creating audit '{0}' on server {3} instance '{4}'. (SSA0012) - CreateFolder = Creating folder {0}. (SSA0013) - AddFilter = Setting filter '{0}' of audit {1} on server '{2}' instance '{3}' to the desired state. (SSA0014) - FailedAddFilter = Failed setting filter '{0}' of audit {1} on server '{2}' instance '{3}'. (SSA0015) - ImpossibleFileCombination = Both MaximumFiles and MaximumRolloverFiles have been defined. This is not a supported configuration. (SSA0016) - ForceNotEnabled = Unable to re-create the server audit. The server audit needs to be re-created but the configuration has not opt-in to re-create the audit. To opt-in set the parameter Force to $true. (SSA0017) -'@ diff --git a/tests/Unit/DSC_SqlServerAudit.Tests.ps1 b/tests/Unit/DSC_SqlServerAudit.Tests.ps1 deleted file mode 100644 index 1d9762679..000000000 --- a/tests/Unit/DSC_SqlServerAudit.Tests.ps1 +++ /dev/null @@ -1,816 +0,0 @@ -<# - .SYNOPSIS - Unit test for DSC_SqlAudit DSC resource. -#> - -# Suppressing this rule because Script Analyzer does not understand Pester's syntax. -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -# Suppressing this rule because tests are mocking passwords in clear text. -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies has not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } -} - -BeforeAll { - $script:dscModuleName = 'SqlServerDsc' - $script:dscResourceName = 'DSC_SqlServerAudit' - - $script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Unit' - - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - - # Loading mocked classes - Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') - - # Load the correct SQL Module stub - $script:stubModuleName = Import-SQLModuleStub -PassThru - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - Restore-TestEnvironment -TestEnvironment $script:testEnvironment - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Unload the stub module. - Remove-SqlModuleStub -Name $script:stubModuleName - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force -} - -Describe 'SqlAudit\Get-TargetResource' -Tag 'Get' { - BeforeAll { - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - Name = 'FileAudit' - # TODO: Remove this - Verbose = $true - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockGetTargetResourceParameters = $script:mockDefaultParameters.Clone() - } - } - - Context 'When the system is in the desired state' { - Context 'When the audit is present' { - BeforeAll { - Mock -CommandName Connect-SQL -MockWith { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{ - # TODO: get the correct values for this. - 'FileAudit' = New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Name' -Value 'FileAudit' -PassThru | - Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | - Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force - } - } -PassThru -Force - ) - ) - } - } - - It 'Should call the mock function Connect-SQL' { - InModuleScope -ScriptBlock { - { Get-TargetResource @mockGetTargetResourceParameters } | Should -Not -Throw - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the desired state as present' { - InModuleScope -ScriptBlock { - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Present' - $result.Enabled | Should -BeTrue - } - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Name | Should -Be $mockGetTargetResourceParameters.Name - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - } - } - - It 'Should return the correct value for the rest of the properties' { - InModuleScope -ScriptBlock { - $result = Get-TargetResource @mockGetTargetResourceParameters - - # TODO: get the correct values for this. - $result.DestinationType | Should -Be 'File' - $result.FilePath | Should -BeNullOrEmpty - $result.Filter | Should -BeNullOrEmpty - $result.MaximumFiles | Should -BeNullOrEmpty - $result.MaximumFileSize | Should -BeNullOrEmpty - $result.MaximumFileSizeUnit | Should -BeNullOrEmpty - $result.MaximumRolloverFiles | Should -BeNullOrEmpty - $result.OnFailure | Should -BeNullOrEmpty - $result.QueueDelay | Should -BeNullOrEmpty - $result.ReserveDiskSpace | Should -BeNullOrEmpty - } - } - } - - Context 'When the audit is absent' { - BeforeAll { - Mock -CommandName Connect-SQL -MockWith { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'Audits' -Value { - return @{ - 'FileAudit' = New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Name' -Value 'FileAudit' -PassThru | - Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | - Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force - } - } -PassThru -Force - ) - ) - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.Name = 'UnknownName' - } - } - - It 'Should call the mock function Connect-SQL' { - InModuleScope -ScriptBlock { - { Get-TargetResource @mockGetTargetResourceParameters } | Should -Not -Throw - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the desired state as present' { - InModuleScope -ScriptBlock { - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Absent' - $result.Enabled | Should -BeFalse - } - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Name | Should -Be $mockGetTargetResourceParameters.Name - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - } - } - - It 'Should return the correct value for the rest of the properties' { - InModuleScope -ScriptBlock { - $result = Get-TargetResource @mockGetTargetResourceParameters - - # TODO: get the correct values for this. - $result.DestinationType | Should -BeNullOrEmpty - $result.FilePath | Should -BeNullOrEmpty - $result.Filter | Should -BeNullOrEmpty - $result.MaximumFiles | Should -BeNullOrEmpty - $result.MaximumFileSize | Should -BeNullOrEmpty - $result.MaximumFileSizeUnit | Should -BeNullOrEmpty - $result.MaximumRolloverFiles | Should -BeNullOrEmpty - $result.OnFailure | Should -BeNullOrEmpty - $result.QueueDelay | Should -BeNullOrEmpty - $result.ReserveDiskSpace | Should -BeNullOrEmpty - } - } - } - } -} - -Describe 'SqlAudit\Test-TargetResource' -Tag 'Test' { - BeforeAll { - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - Name = 'FileAudit' - # TODO: Remove this - Verbose = $true - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockTestTargetResourceParameters = $script:mockDefaultParameters.Clone() - } - } - - Context 'When the system is in the desired state' { - Context 'When the audit is present' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - Ensure = 'Present' - Name = 'FileAudit' - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - DestinationType = 'File' - FilePath = $null - Filter = $null - MaximumFiles = $null - MaximumFileSize = $null - MaximumFileSizeUnit = $null - MaximumRolloverFiles = $null - OnFailure = $null - QueueDelay = $null - ReserveDiskSpace = $null - Enabled = $false - } - } - } - - It 'Should return the property as in desired state' -ForEach @( - # TODO: add all properties - @{ - MockPropertyName = 'DestinationType' - MockExpectedValue = 'File' - } - ) { - InModuleScope -Parameters $_ -ScriptBlock { - $mockTestTargetResourceParameters.$MockPropertyName = $MockExpectedValue - - $result = Test-TargetResource @mockTestTargetResourceParameters - - $result | Should -BeTrue - } - } - } - - Context 'When the audit is absent' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - Ensure = 'Absent' - Name = 'FileAudit' - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - DestinationType = $null - FilePath = $null - Filter = $null - MaximumFiles = $null - MaximumFileSize = $null - MaximumFileSizeUnit = $null - MaximumRolloverFiles = $null - OnFailure = $null - QueueDelay = $null - ReserveDiskSpace = $null - Enabled = $false - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $mockTestTargetResourceParameters.Ensure = 'Absent' - } - } - - It 'Should return the property as in desired state' { - InModuleScope -ScriptBlock { - $result = Test-TargetResource @mockTestTargetResourceParameters - - $result | Should -BeTrue - } - } - } - } -} - - # $mockServerName = 'SERVER01' - # $mockInstanceName = 'INSTANCE' - # $mockAuditName = 'FileAudit' - - # $mockAuditObject = New-Object -TypeName Object | - # Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAuditName -PassThru | - # Add-Member -MemberType NoteProperty -Name 'DestinationType' -Value 'File' -PassThru | - # Add-Member -MemberType NoteProperty -Name 'FilePath' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'Filter' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'MaximumFiles' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'MaximumFileSize' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'MaximumFileSizeUnit' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'MaximumRolloverFiles' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'OnFailure' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'QueueDelay' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'ReserveDiskSpace' -Value $null -PassThru | - # Add-Member -MemberType NoteProperty -Name 'Enabled' -Value $true -PassThru -Force - - # $mockConnectSql = { - # return New-Object -TypeName Object | - # Add-Member -MemberType ScriptProperty -Name 'Audits' -Value { - # return @( - # @{ - # $mockAuditName = $mockAuditObject - # } - # ) - # } -PassThru -Force - # } - - # $defaultParameters = @{ - # InstanceName = $mockInstanceName - # ServerName = $mockServerName - # Name = $mockAuditName - # Verbose = $true - # } - - # BeforeAll { - # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - # } - - # Context 'When the system is not in the desired state' { - # BeforeEach { - # $testParameters = $defaultParameters.Clone() - # $testParameters.Name = 'UnknownAudit' - # } - - # It 'Should call the mock function Connect-SQL' { - # { Get-TargetResource @testParameters } | Should -Not -Throw - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # It 'Should return the desired state as absent' { - # $result = Get-TargetResource @testParameters - - # $result.Ensure | Should -Be 'Absent' - # $result.Enabled | Should -BeFalse - # } - - # It 'Should return the same values as passed as parameters' { - # $result = Get-TargetResource @testParameters - - # $result.Name | Should -Be $testParameters.Name - # $result.ServerName | Should -Be $testParameters.ServerName - # $result.InstanceName | Should -Be $testParameters.InstanceName - # } - - # It 'Should return $null for the rest of the properties' { - # $result = Get-TargetResource @testParameters - - # $result.DestinationType | Should -BeNullOrEmpty - # $result.FilePath | Should -BeNullOrEmpty - # $result.Filter | Should -BeNullOrEmpty - # $result.MaximumFiles | Should -BeNullOrEmpty - # $result.MaximumFileSize | Should -BeNullOrEmpty - # $result.MaximumFileSizeUnit | Should -BeNullOrEmpty - # $result.MaximumRolloverFiles | Should -BeNullOrEmpty - # $result.OnFailure | Should -BeNullOrEmpty - # $result.QueueDelay | Should BeNullOrEmpty - # $result.ReserveDiskSpace | Should -BeNullOrEmpty - # } - # } - - # Context 'When the system is in the desired state' { - # BeforeEach { - # $testParameters = $defaultParameters.Clone() - # } - - - # } - # } - - # Describe 'DSC_SqlServerAudit\Test-TargetResource' -Tag 'Test' { - # BeforeEach { - # $testParameters = $defaultParameters.Clone() - - # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - # } - - # Context 'When the system is not in the desired state' { - # # Make sure the mock does not return the correct endpoint - # $mockDynamicEndpointName = $mockOtherEndpointName - - # It 'Should return that desired state is absent when wanted desired state is to be Present (using default values)' { - # $testParameters.Add('Ensure', 'Present') - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # It 'Should return that desired state is absent when wanted desired state is to be Present (setting all parameters)' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Make sure the mock do return the correct endpoint - # $mockDynamicEndpointName = $mockEndpointName - - # It 'Should return that desired state is absent when wanted desired state is to be Absent' { - # $testParameters.Add('Ensure', 'Absent') - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener port - # $mockDynamicEndpointName = $mockEndpointName - # $mockDynamicEndpointListenerPort = $mockOtherEndpointListenerPort - - # Context 'When listener port is not in desired state' { - # It 'Should return that desired state is absent' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } - - # # Make sure the mock do return the correct endpoint listener port - # $mockDynamicEndpointListenerPort = $mockEndpointListenerPort - - # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener IP address - # $mockDynamicEndpointName = $mockEndpointName - # $mockDynamicEndpointListenerIpAddress = $mockOtherEndpointListenerIpAddress - - # Context 'When listener IP address is not in desired state' { - # It 'Should return that desired state is absent' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } - - # # Make sure the mock do return the correct endpoint listener IP address - # $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress - - # # Make sure the mock do return the correct endpoint, but does not return the correct endpoint owner - # $mockDynamicEndpointName = $mockEndpointName - # $mockDynamicEndpointOwner = $mockOtherEndpointOwner - - # Context 'When listener Owner is not in desired state' { - # It 'Should return that desired state is absent' { - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Owner', $mockEndpointOwner) - - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } - - # # Make sure the mock do return the correct endpoint owner - # $mockDynamicEndpointOwner = $mockEndpointOwner - # } - - # Context 'When the system is in the desired state' { - # # Make sure the mock do return the correct endpoint - # $mockDynamicEndpointName = $mockEndpointName - - # It 'Should return that desired state is present when wanted desired state is to be Present (using default values)' { - # $result = Test-TargetResource @testParameters - # $result | Should -Be $true - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Make sure the mock does not return the correct endpoint - # $mockDynamicEndpointName = $mockOtherEndpointName - - # It 'Should return that desired state is present when wanted desired state is to be Absent' { - # $testParameters.Add('Ensure', 'Absent') - - # $result = Test-TargetResource @testParameters - # $result | Should -Be $true - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - # } - - # Assert-VerifiableMock - # } - - # Describe 'DSC_SqlServerAudit\Set-TargetResource' -Tag 'Set' { - # BeforeEach { - # $testParameters = $defaultParameters.Clone() - - # Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - # Mock -CommandName New-Object -MockWith $mockNewObjectEndPoint -ParameterFilter $mockNewObjectEndPoint_ParameterFilter -Verifiable - # } - - # Context 'When the system is not in the desired state' { - # # Make sure the mock do return the correct endpoint - # $mockDynamicEndpointName = $mockEndpointName - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Create() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call the method Create when desired state is to be Present (using default values)' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Absent' - # } - # } -Verifiable - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $true - # $script:mockMethodStartRan | Should -Be $true - # $script:mockMethodAlterRan | Should -Be $false - # $script:mockMethodDropRan | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Create() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call the method Create when desired state is to be Present (setting all parameters)' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Absent' - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $true - # $script:mockMethodStartRan | Should -Be $true - # $script:mockMethodAlterRan | Should -Be $false - # $script:mockMethodDropRan | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Drop() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call the method Drop when desired state is to be Absent' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Absent') - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $false - # $script:mockMethodDropRan | Should -Be $true - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Alter() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call Alter method when listener port is not in desired state' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockOtherEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $true - # $script:mockMethodDropRan | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Alter() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call Alter method when listener IP address is not in desired state' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockOtherEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $true - # $script:mockMethodDropRan | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Set all method call tests variables to $false - # $script:mockMethodCreateRan = $false - # $script:mockMethodStartRan = $false - # $script:mockMethodAlterRan = $false - # $script:mockMethodDropRan = $false - - # # Set what the expected endpoint name should be when Alter() method is called. - # $mockExpectedNameWhenCallingMethod = $mockEndpointName - - # It 'Should call Alter method when Owner is not in desired state' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Present') - # $testParameters.Add('Port', $mockEndpointListenerPort) - # $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) - # $testParameters.Add('Owner', $mockOtherEndpointOwner) - - # { Set-TargetResource @testParameters } | Should -Not -Throw - # $script:mockMethodCreateRan | Should -Be $false - # $script:mockMethodStartRan | Should -Be $false - # $script:mockMethodAlterRan | Should -Be $true - # $script:mockMethodDropRan | Should -Be $false - - # Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - # } - - # # Make sure the mock does not return the correct endpoint - # $mockDynamicEndpointName = $mockOtherEndpointName - - # Context 'When endpoint is missing when Ensure is set to Present' { - # It 'Should throw the correct error' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # Owner = $mockEndpointOwner - # } - # } -Verifiable - - # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) - # } - # } - - # Context 'When endpoint is missing when Ensure is set to Absent' { - # It 'Should throw the correct error' { - # Mock -CommandName Get-TargetResource -MockWith { - # return @{ - # Ensure = 'Present' - # Port = $mockEndpointListenerPort - # IpAddress = $mockEndpointListenerIpAddress - # Owner = $mockEndpointOwner - # } - # } -Verifiable - - # $testParameters.Add('Ensure', 'Absent') - - # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.EndpointNotFound -f $testParameters.EndpointName) - # } - # } - - # Context 'When Connect-SQL returns nothing' { - # It 'Should throw the correct error' { - # Mock -CommandName Get-TargetResource -Verifiable - # Mock -CommandName Connect-SQL -MockWith { - # return $null - # } - - # { Set-TargetResource @testParameters } | Should -Throw ($script:localizedData.NotConnectedToInstance -f $testParameters.ServerName, $testParameters.InstanceName) - # } - # } - # } - - - # Assert-VerifiableMock - # } -#} From 94bb43ee3ae882ffd6c70c9799067d4770e6fc96 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 16:32:09 +0200 Subject: [PATCH 36/71] Fix SqlAudit --- source/Classes/020.SqlAudit.ps1 | 295 +++++++++++++++++++++++------ source/en-US/SqlAudit.strings.psd1 | 8 +- 2 files changed, 245 insertions(+), 58 deletions(-) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index 75556d5ff..41709b74e 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -7,6 +7,11 @@ The `SqlAudit` DSC resource is used to create, modify, or remove server audits. + The built-in parameter **PSDscRunAsCredential** can be used to run the resource + as another user. The resource will then authenticate to the SQL Server + instance as that user. It also possible to instead use impersonation by the + parameter **Credential**. + ## Requirements * Target machine must be running Windows Server 2012 or later. @@ -18,13 +23,14 @@ 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+SqlAudit). - ### `PSDscRunAsCredential` not supported + ### Property **Reasons** does not work with **PSDscRunAsCredential** - The built-in property `PSDscRunAsCredential` does not work with class-based - resources that using advanced type like the parameter `Permission` does. - Use the parameter `Credential` instead of `PSDscRunAsCredential`. + When using the built-in parameter `**PSDscRunAsCredential** the read-only + property `Reasons` will return empty values for Code and Phrase. The + built-in property **PSDscRunAsCredential** does not work with class-based + resources that using advanced type like the parameter `Reasons` have. - ### Using `Credential` property. + ### Using **Credential** property. SQL Authentication and Group Managed Service Accounts is not supported as impersonation credentials. Currently only Windows Integrated Security is @@ -51,7 +57,7 @@ .PARAMETER LogType Specifies the to which log an audit logs to. Mutually exclusive to parameter - **Path**. This can be set to `SecurityLog` or `ApplicationLog`. + **Path**. .PARAMETER Path Specifies the destination path for a file audit. Mutually exclusive to parameter @@ -62,21 +68,23 @@ .PARAMETER MaximumFiles Specifies the number of files on disk. Mutually exclusive to parameter - **MaximumRolloverFiles**. + **MaximumRolloverFiles**. Mutually exclusive to parameter **LogType**. .PARAMETER MaximumFileSize Specifies the maximum file size in units by parameter **MaximumFileSizeUnit**. If this is specified the parameter **MaximumFileSizeUnit** must also be - specified. + specified. Mutually exclusive to parameter **LogType**. Minimum allowed value + is 2 (MB). It also allowed to set the value to 0 which mean unlimited file + size. .PARAMETER MaximumFileSizeUnit - Specifies the unit that is used for the file size. this can be KB, MB or GB. + Specifies the unit that is used for the file size. If this is specified the parameter **MaximumFileSize** must also be - specified. + specified. Mutually exclusive to parameter **LogType**. .PARAMETER MaximumRolloverFiles Specifies the amount of files on disk before SQL Server starts reusing - the files. Mutually exclusive to parameter **MaximumFiles**. + the files. Mutually exclusive to parameter **MaximumFiles** and **LogType**. .PARAMETER OnFailure Specifies what should happen when writing events to the store fails. @@ -89,7 +97,7 @@ .PARAMETER ReserveDiskSpace Specifies if the needed file space should be reserved. only needed - when writing to a file log. + when writing to a file log. Mutually exclusive to parameter **LogType**. .PARAMETER Enabled Specifies if the audit should be enabled. Defaults to `$false`. @@ -125,8 +133,7 @@ This example shows how to call the resource using Invoke-DscResource. #> -# TODO: verify RunAsCredential = 'NotSupported' - remove in comment-based help -[DscResource()] +[DscResource(RunAsCredential = 'Optional')] class SqlAudit : ResourceBase { <# @@ -164,12 +171,13 @@ class SqlAudit : ResourceBase $AuditFilter [DscProperty()] - [System.UInt32] + [Nullable[System.UInt32]] $MaximumFiles [DscProperty()] - [ValidateRange(2, 2147483647)] - [System.UInt32] + # There is an extra evaluation in AssertProperties() for this range. + [ValidateRange(0, 2147483647)] + [Nullable[System.UInt32]] $MaximumFileSize [DscProperty()] @@ -178,7 +186,8 @@ class SqlAudit : ResourceBase $MaximumFileSizeUnit [DscProperty()] - [System.UInt32] + [ValidateRange(0, 2147483647)] + [Nullable[System.UInt32]] $MaximumRolloverFiles [DscProperty()] @@ -187,8 +196,9 @@ class SqlAudit : ResourceBase $OnFailure [DscProperty()] - [ValidateRange(1000, 2147483647)] - [System.UInt32] + # There is an extra evaluation in AssertProperties() for this range. + [ValidateRange(0, 2147483647)] + [Nullable[System.UInt32]] $QueueDelay [DscProperty()] @@ -197,20 +207,19 @@ class SqlAudit : ResourceBase $AuditGuid [DscProperty()] - [System.Boolean] + [Nullable[System.Boolean]] $ReserveDiskSpace [DscProperty()] - [System.Boolean] + [Nullable[System.Boolean]] $Enabled [DscProperty()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present' + [Ensure] + $Ensure = [Ensure]::Present [DscProperty()] - [System.Boolean] + [Nullable[System.Boolean]] $Force [DscProperty()] @@ -223,12 +232,14 @@ class SqlAudit : ResourceBase SqlAudit() : base () { + # TODO:_Rename this to ExcludeDscProperties or ExcludeProperties # These properties will not be enforced. $this.notEnforcedProperties = @( 'ServerName' 'InstanceName' 'Name' 'Credential' + 'Force' ) } @@ -291,6 +302,13 @@ class SqlAudit : ResourceBase #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { + Write-Verbose -Message ( + $this.localizedData.EvaluateServerAudit -f @( + $properties.Name, + $properties.InstanceName + ) + ) + $currentStateCredential = $null if ($this.Credential) @@ -306,25 +324,55 @@ class SqlAudit : ResourceBase ) } + <# + Only set key property Name if the audit exist. Base class will set it + and handle Ensure. + #> $currentState = @{ - Credential = $currentStateCredential + Credential = $currentStateCredential + InstanceName = $properties.InstanceName + ServerName = $this.ServerName + Force = $this.Force } - Write-Verbose -Message ( - $this.localizedData.EvaluateServerPermissionForPrincipal -f @( - $properties.Name, - $properties.InstanceName - ) - ) - $serverObject = $this.GetServerObject() $auditObject = $serverObject | - Get-SqlDscAudit -Name $this.Name -ErrorAction 'SilentlyContinue' + Get-SqlDscAudit -Name $properties.Name -ErrorAction 'SilentlyContinue' - # If permissions was returned, build the current permission array of [ServerPermission]. if ($auditObject) { + $currentState.Name = $properties.Name + + if ($auditObject.DestinationType -in @('ApplicationLog', 'SecurityLog')) + { + $currentState.LogType = $auditObject.DestinationType + } + + $currentState.Path = $auditObject.FilePath + $currentState.AuditFilter = $auditObject.Filter + $currentState.MaximumFiles = [System.UInt32] $auditObject.MaximumFiles + $currentState.MaximumFileSize = [System.UInt32] $auditObject.MaximumFileSize + + if ($auditObject.MaximumFileSizeUnit) + { + $convertedMaximumFileSizeUnit = ( + @{ + Mb = 'Megabyte' + Gb = 'Gigabyte' + Tb = 'Terabyte' + } + ).($auditObject.MaximumFileSizeUnit) + + $currentState.MaximumFileSizeUnit = $convertedMaximumFileSizeUnit + } + + $currentState.MaximumRolloverFiles = [System.UInt32] $auditObject.MaximumRolloverFiles + $currentState.OnFailure = $auditObject.OnFailure + $currentState.QueueDelay = [System.UInt32] $auditObject.QueueDelay + $currentState.AuditGuid = $auditObject.Guid + $currentState.ReserveDiskSpace = $auditObject.ReserveDiskSpace + $currentState.Enabled = $auditObject.Enabled } return $currentState @@ -339,17 +387,97 @@ class SqlAudit : ResourceBase hidden [void] Modify([System.Collections.Hashtable] $properties) { $serverObject = $this.GetServerObject() + $auditObject = $null - # if (-not $isLogin) - # { - # $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( - # $this.Name, - # $this.InstanceName - # ) + if ($properties.Keys -contains 'Ensure') + { + # Evaluate the desired state for property Ensure. + switch ($properties.Ensure) + { + 'Present' + { + # Get all properties that has an assigned value. + $assignedDscProperties = $this | Get-DscProperty -HasValue -Type @( + 'Key' + 'Mandatory' + 'Optional' + ) -ExcludeName @( + # Remove properties that is not an audit property. + 'InstanceName' + 'ServerName' + 'Enabled' + 'Ensure' + 'Force' + 'Credential' + + # Remove this audit property since it must be handled later. + 'Enabled' + ) + + if ($assignedDscProperties.Keys -notcontains 'LogType' -and $assignedDscProperties.Keys -notcontains 'Path') + { + New-InvalidOperationException -Message $this.localizedData.CannotCreateNewAudit + } + + # Create the audit since it was missing. Always created disabled. + $auditObject = $serverObject | New-SqlDscAudit @assignedDscProperties -Force -PassThru + } + + 'Absent' + { + # Remove the audit since it was present + $serverObject | Remove-SqlDscAudit -Name $this.Name -Force + } + } + } + else + { + <# + Update any properties not in desired state if the audit should be present. + At this point it is assumed the audit exist since Ensure property was + in desired state. + + If the desired state happens to be Absent then ignore any properties not + in desired state (user have in that case wrongly added properties to an + "absent configuration"). + #> + if ($this.Ensure -eq [Ensure]::Present) + { + $auditObject = $serverObject | + Get-SqlDscAudit -Name $this.Name -ErrorAction 'Stop' - # New-InvalidOperationException -Message $missingPrincipalMessage - # } + if ($auditObject) + { + } + # TODO: Should evaluate if Path is assigned and DestinationType is *Log it should recreate if Force is $true + + # TODO: Should evaluate if LogType is assigned and DestinationType is File it should recreate if Force is $true + + # TODO: Should evaluate DestinationType so that is does not try to set File properties when type is Log (and LogType and Path is not also present) + } + } + + <# + If there is an audit object either from a newly created or fetched from + current state, and if the desired state is Present, evaluate if the + audit should be enable or disable. + #> + if ($auditObject -and $this.Ensure -eq [Ensure]::Present -and $properties.Keys -contains 'Enabled') + { + switch ($properties.Enabled) + { + $true + { + $serverObject | Enable-SqlDscAudit -Name $this.Name -Force + } + + $false + { + $serverObject | Disable-SqlDscAudit -Name $this.Name -Force + } + } + } } <# @@ -358,7 +486,7 @@ class SqlAudit : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission + # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. $assertBoundParameterParameters = @{ BoundParameterList = $properties MutuallyExclusiveList1 = @( @@ -371,7 +499,25 @@ class SqlAudit : ResourceBase Assert-BoundParameter @assertBoundParameterParameters - # Get all assigned permission properties. + # LogType is mutually exclusive from any of the File audit properties. + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + MutuallyExclusiveList1 = @( + 'LogType' + ) + MutuallyExclusiveList2 = @( + 'Path' + 'MaximumFiles' + 'MaximumFileSize' + 'MaximumFileSizeUnit' + 'MaximumRolloverFiles' + 'ReserveDiskSpace' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + # Get all assigned *FileSize properties. $assignedSizeProperty = $properties.Keys.Where({ $_ -in @( 'MaximumFileSize', @@ -379,18 +525,53 @@ class SqlAudit : ResourceBase ) }) - # TODO: Above count should be either 0 or 2, if 1 throw an error. - # if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) - # { - # $errorMessage = $this.localizedData.MustAssignOnePermissionProperty + <# + Neither or both of the properties MaximumFileSize and MaximumFileSizeUnit + must be assigned. + #> + if ($assignedSizeProperty.Count -eq 1) + { + $errorMessage = $this.localizedData.BothFileSizePropertiesMustBeSet - # New-InvalidArgumentException -ArgumentName 'Permission, PermissionToInclude, PermissionToExclude' -Message $errorMessage - # } + New-InvalidArgumentException -ArgumentName 'MaximumFileSize, MaximumFileSizeUnit' -Message $errorMessage + } - # TODO: Test path - # if (-not (Test-Path -Path $_)) - # { - # throw ($script:localizedData.Audit_PathParameterValueInvalid -f $_) - # } + <# + Since we cannot use [ValidateScript()], and it is no possible to exclude + a value in the [ValidateRange()], evaluate so 1 is not assigned. + #> + if ($properties.Keys -contains 'MaximumFileSize' -and $properties.MaximumFileSize -eq 1) + { + $errorMessage = $this.localizedData.MaximumFileSizeValueInvalid + + New-InvalidArgumentException -ArgumentName 'MaximumFileSize' -Message $errorMessage + } + + <# + Since we cannot use [ValidateScript()], and it is no possible to exclude + a value in the [ValidateRange()], evaluate so 1-999 is not assigned. + #> + if ($properties.Keys -contains 'QueueDelay' -and $properties.QueueDelay -in 1..999) + { + $errorMessage = $this.localizedData.QueueDelayValueInvalid + + New-InvalidArgumentException -ArgumentName 'QueueDelay' -Message $errorMessage + } + + # ReserveDiskSpace can only be used with MaximumFiles. + if ($properties.Keys -contains 'ReserveDiskSpace' -and $properties.Keys -notcontains 'MaximumFiles') + { + $errorMessage = $this.localizedData.BothFileSizePropertiesMustBeSet + + New-InvalidArgumentException -ArgumentName 'ReserveDiskSpace' -Message $errorMessage + } + + # Test so that the path exist. + if ($properties.Keys -contains 'Path' -and -not (Test-Path -Path $properties.Path)) + { + $errorMessage = $this.localizedData.PathInvalid + + New-InvalidArgumentException -ArgumentName 'Path' -Message $errorMessage + } } } diff --git a/source/en-US/SqlAudit.strings.psd1 b/source/en-US/SqlAudit.strings.psd1 index 318dacb8d..556b6be31 100644 --- a/source/en-US/SqlAudit.strings.psd1 +++ b/source/en-US/SqlAudit.strings.psd1 @@ -9,5 +9,11 @@ ConvertFrom-StringData @' # None ## Strings directly used by the derived class SqlDatabasePermission. - #EvaluateServerPermissionForPrincipal = Evaluate the current permissions for the principal '{0}' on the instance '{1}'. (SP0001) + BothFileSizePropertiesMustBeSet = Both the parameter MaximumFileSize and MaximumFileSizeUnit must be assigned. (SA0001) + ReservDiskSpaceWithoutMaximumFiles = The parameter ReservDiskSpace can only be used together with the parameter MaximumFiles. (SA0002) + PathInvalid = The path '{0}' does not exist. Audit file can only be created in a path that already exist and where the SQL Server instance has permission to write. (SA0003) + EvaluateServerAudit = Evaluate the current audit '{0}' on the instance '{1}'. (SA0004) + MaximumFileSizeValueInvalid = The maximum file size must be set to a value of 0 or a value between 2 and 2147483647. (SA0004) + QueueDelayValueInvalid = The queue delay must be set to a value of 0 or a value between 1000 and 2147483647. + CannotCreateNewAudit = Cannot create a new audit because neither of the properties LogType or Path is specified. One of those properties must be specified to create a new audit. '@ From 3baf71361496ec0ad3b1565c4611e5b1eb2d1ebd Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 16:35:35 +0200 Subject: [PATCH 37/71] Fix integration tests and examples --- appveyor.yml | 2 +- azure-pipelines.yml | 2 +- source/Examples/README.md | 2 +- .../1-AddFileAudit.ps1 | 7 +- .../2-AddSecuritylogAudit.ps1 | 6 +- ...3-AddSecuritylogAuditOnFailureShutdown.ps1 | 17 ++- .../4-AddSecuritylogAuditWithFilter.ps1 | 18 +-- .../5-RemoveAudit.ps1 | 12 +- ...-AddServerAuditSpecificationAdminAudit.ps1 | 6 +- ...-AddServerAuditSpecificationLoginAudit.ps1 | 6 +- .../3-AddServerAuditAuditChange.ps1 | 6 +- .../4-AddMultipleServerAudits.ps1 | 116 +++++++++--------- ...ps1 => DSC_SqlAudit.Integration.Tests.ps1} | 14 +-- ...dit.config.ps1 => DSC_SqlAudit.config.ps1} | 43 ++++--- ...erAuditSpecification.Integration.Tests.ps1 | 74 +---------- ...DSC_SqlServerAuditSpecification.config.ps1 | 100 +++------------ 16 files changed, 149 insertions(+), 282 deletions(-) rename source/Examples/Resources/{SqlServerAudit => SqlAudit}/1-AddFileAudit.ps1 (79%) rename source/Examples/Resources/{SqlServerAudit => SqlAudit}/2-AddSecuritylogAudit.ps1 (81%) rename source/Examples/Resources/{SqlServerAudit => SqlAudit}/3-AddSecuritylogAuditOnFailureShutdown.ps1 (54%) rename source/Examples/Resources/{SqlServerAudit => SqlAudit}/4-AddSecuritylogAuditWithFilter.ps1 (52%) rename source/Examples/Resources/{SqlServerAudit => SqlAudit}/5-RemoveAudit.ps1 (58%) rename tests/Integration/{DSC_SqlServerAudit.Integration.Tests.ps1 => DSC_SqlAudit.Integration.Tests.ps1} (94%) rename tests/Integration/{DSC_SqlServerAudit.config.ps1 => DSC_SqlAudit.config.ps1} (78%) diff --git a/appveyor.yml b/appveyor.yml index 42d5eea5e..83042069c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -78,7 +78,7 @@ test_script: #'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' #'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' #'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' ## Group 4 #'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 51ddffece..a83e97110 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -198,7 +198,7 @@ stages: 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' 'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' 'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' 'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' # Group 4 'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' diff --git a/source/Examples/README.md b/source/Examples/README.md index 58d35b729..98c764eb1 100644 --- a/source/Examples/README.md +++ b/source/Examples/README.md @@ -15,7 +15,7 @@ These are the links to the examples for each individual resource. - [SqlAGReplica](Resources/SqlAGReplica) - [SqlAlias](Resources/SqlAlias) - [SqlAlwaysOnService](Resources/SqlAlwaysOnService) -- [SqlServerAudit](Resources/SqlServerAudit) +- [SqlAudit](Resources/SqlAudit) - [SqlServerAuditSpecification](Resources/SqlServerAuditSpecification) - [SqlDatabase](Resources/SqlDatabase) - [SqlDatabaseDefaultLocation](Resources/SqlDatabaseDefaultLocation) diff --git a/source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 b/source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 similarity index 79% rename from source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 rename to source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 index f483b556d..50df5debf 100644 --- a/source/Examples/Resources/SqlServerAudit/1-AddFileAudit.ps1 +++ b/source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 @@ -16,19 +16,18 @@ Configuration Example node localhost { - SqlServerAudit FileAudit_Server + SqlAudit FileAudit_Server { Ensure = 'Present' ServerName = 'SQL2019-01' InstanceName = 'INST01' Name = 'FileAudit' - DestinationType = 'File' - FilePath = 'C:\Temp\audit' + Path = 'C:\Temp\audit' MaximumFileSize = 10 MaximumFileSizeUnit = 'MB' MaximumRolloverFiles = 11 Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 b/source/Examples/Resources/SqlAudit/2-AddSecuritylogAudit.ps1 similarity index 81% rename from source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 rename to source/Examples/Resources/SqlAudit/2-AddSecuritylogAudit.ps1 index 7e9b77e5c..178da00eb 100644 --- a/source/Examples/Resources/SqlServerAudit/2-AddSecuritylogAudit.ps1 +++ b/source/Examples/Resources/SqlAudit/2-AddSecuritylogAudit.ps1 @@ -16,15 +16,15 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server + SqlAudit SecurityLogAudit_Server { Ensure = 'Present' ServerName = 'sqltest.company.local' InstanceName = 'DSC' Name = 'SecLogAudit' - DestinationType = 'SecurityLog' + LogType = 'SecurityLog' Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 b/source/Examples/Resources/SqlAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 similarity index 54% rename from source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 rename to source/Examples/Resources/SqlAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 index 642c539cd..84eeac510 100644 --- a/source/Examples/Resources/SqlServerAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 +++ b/source/Examples/Resources/SqlAudit/3-AddSecuritylogAuditOnFailureShutdown.ps1 @@ -17,16 +17,15 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server + SqlAudit SecurityLogAudit_Server { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit' - DestinationType = 'SecurityLog' - OnFailure = 'SHUTDOWN' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + OnFailure = 'Shutdown' + Enabled = $true + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 b/source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 similarity index 52% rename from source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 rename to source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 index c239011f7..e2f58dda7 100644 --- a/source/Examples/Resources/SqlServerAudit/4-AddSecuritylogAuditWithFilter.ps1 +++ b/source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 @@ -17,16 +17,16 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server + SqlAudit SecurityLogAudit_Server { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit' - DestinationType = 'SecurityLog' - Enabled = $true - Filter = '([server_principal_name] like ''%ADMINISTRATOR'')' - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit' + LogType = 'SecurityLog' + Enabled = $true + Filter = '([server_principal_name] like ''%ADMINISTRATOR'')' + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 b/source/Examples/Resources/SqlAudit/5-RemoveAudit.ps1 similarity index 58% rename from source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 rename to source/Examples/Resources/SqlAudit/5-RemoveAudit.ps1 index d237c88dd..c963b451f 100644 --- a/source/Examples/Resources/SqlServerAudit/5-RemoveAudit.ps1 +++ b/source/Examples/Resources/SqlAudit/5-RemoveAudit.ps1 @@ -16,13 +16,13 @@ Configuration Example node localhost { - SqlServerAudit FileAudit_Server + SqlAudit FileAudit_Server { - Ensure = 'Absent' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'FileAudit' - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Absent' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'FileAudit' + Credential = $SqlAdministratorCredential } } } diff --git a/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 index 28015df6a..0288f2607 100644 --- a/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 +++ b/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 @@ -16,13 +16,13 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server + SqlAudit SecurityLogAudit_Server { Ensure = 'Present' ServerName = 'sqltest.company.local' InstanceName = 'DSC' Name = 'SecLogAudit' - DestinationType = 'SecurityLog' + LogType = 'SecurityLog' Enabled = $true PsDscRunAsCredential = $SqlAdministratorCredential } @@ -58,7 +58,7 @@ Configuration Example ServerRoleMemberChangeGroup = $true ServerStateChangeGroup = $true TraceChangeGroup = $true - DependsOn = '[SqlServerAudit]SecurityLogAudit_Server' + DependsOn = '[SqlAudit]SecurityLogAudit_Server' PsDscRunAsCredential = $SqlAdministratorCredential } } diff --git a/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 index b5678b709..bf8e5a29b 100644 --- a/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 +++ b/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 @@ -16,13 +16,13 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server + SqlAudit SecurityLogAudit_Server { Ensure = 'Present' ServerName = 'sqltest.company.local' InstanceName = 'DSC' Name = 'SecLogAudit' - DestinationType = 'SecurityLog' + LogType = 'SecurityLog' Enabled = $true PsDscRunAsCredential = $SqlAdministratorCredential } @@ -42,7 +42,7 @@ Configuration Example LogoutGroup = $true SuccessfulDatabaseAuthenticationGroup = $true SuccessfulLoginGroup = $true - DependsOn = '[SqlServerAudit]SecurityLogAudit_Server' + DependsOn = '[SqlAudit]SecurityLogAudit_Server' PsDscRunAsCredential = $SqlAdministratorCredential } } diff --git a/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 index 982e72351..f4fa11bac 100644 --- a/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 +++ b/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 @@ -16,13 +16,13 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server + SqlAudit SecurityLogAudit_Server { Ensure = 'Present' ServerName = 'sqltest.company.local' InstanceName = 'DSC' Name = 'SecLogAudit' - DestinationType = 'SecurityLog' + LogType = 'SecurityLog' Enabled = $true PsDscRunAsCredential = $SqlAdministratorCredential } @@ -37,7 +37,7 @@ Configuration Example Enabled = $true AuditChangeGroup = $true TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]SecurityLogAudit_Server" + DependsOn = "[SqlAudit]SecurityLogAudit_Server" PsDscRunAsCredential = $SqlAdministratorCredential } } diff --git a/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 index d981e2dbc..e73789ca0 100644 --- a/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 +++ b/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 @@ -16,77 +16,75 @@ Configuration Example node localhost { - SqlServerAudit SecurityLogAudit_Server01 + SqlAudit SecurityLogAudit_Server01 { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit01' - DestinationType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit01' + LogType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential } - SqlServerAudit SecurityLogAudit_Server02 + SqlAudit SecurityLogAudit_Server02 { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit02' - DestinationType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'SecLogAudit02' + LogType = 'SecurityLog' + Enabled = $true + PsDscRunAsCredential = $SqlAdministratorCredential } SqlServerAuditSpecification ServerAuditSpecification_AuditAudit { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AuditAudit' - AuditName = 'SecLogAudit01' - Enabled = $true - AuditChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]SecurityLogAudit_Server01" - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AuditAudit' + AuditName = 'SecLogAudit01' + Enabled = $true + AuditChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlAudit]SecurityLogAudit_Server01' + PsDscRunAsCredential = $SqlAdministratorCredential } - - SqlServerAuditSpecification ServerAuditSpecification_AdminAudit { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AdminAudit' - AuditName = 'SecLogAudit02' - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlServerAudit]SecurityLogAudit_Server02" - PsDscRunAsCredential = $SqlAdministratorCredential + Ensure = 'Present' + ServerName = 'sqltest.company.local' + InstanceName = 'DSC' + Name = 'AdminAudit' + AuditName = 'SecLogAudit02' + Enabled = $true + AuditChangeGroup = $true + BackupRestoreGroup = $true + DatabaseObjectChangeGroup = $true + DatabaseObjectOwnershipChangeGroup = $true + DatabaseObjectPermissionChangeGroup = $true + DatabaseOwnershipChangeGroup = $true + DatabasePermissionChangeGroup = $true + DatabasePrincipalChangeGroup = $true + DatabasePrincipalImpersonationGroup = $true + DatabaseRoleMemberChangeGroup = $true + SchemaObjectChangeGroup = $true + SchemaObjectOwnershipChangeGroup = $true + SchemaObjectPermissionChangeGroup = $true + ServerObjectChangeGroup = $true + ServerObjectOwnershipChangeGroup = $true + ServerObjectPermissionChangeGroup = $true + ServerOperationGroup = $true + ServerPermissionChangeGroup = $true + ServerPrincipalChangeGroup = $true + ServerPrincipalImpersonationGroup = $true + ServerRoleMemberChangeGroup = $true + ServerStateChangeGroup = $true + TraceChangeGroup = $true + DependsOn = '[SqlAudit]SecurityLogAudit_Server02' + PsDscRunAsCredential = $SqlAdministratorCredential } } } diff --git a/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 similarity index 94% rename from tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 rename to tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 index 98567a8ef..97a607b23 100644 --- a/tests/Integration/DSC_SqlServerAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 @@ -12,14 +12,14 @@ BeforeDiscovery { Need to define that variables here to be used in the Pester Discover to build the ForEach-blocks. #> - $script:dscResourceFriendlyName = 'SqlServerAudit' + $script:dscResourceFriendlyName = 'SqlAudit' $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" } BeforeAll { # Need to define the variables here which will be used in Pester Run. $script:dscModuleName = 'SqlServerDsc' - $script:dscResourceFriendlyName = 'SqlServerAudit' + $script:dscResourceFriendlyName = 'SqlAudit' $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" $script:testEnvironment = Initialize-TestEnvironment ` @@ -91,8 +91,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 - $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.Path1 $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 @@ -153,7 +152,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.LogType | Should -Be $ConfigurationData.AllNodes.LogType2 $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 } @@ -212,7 +211,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 + $resourceCurrentState.LogType | Should -Be $ConfigurationData.AllNodes.LogType2 $resourceCurrentState.Filter | Should -BeNullOrEmpty } @@ -271,8 +270,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -BeNullOrEmpty - $resourceCurrentState.FilePath | Should -BeNullOrEmpty + $resourceCurrentState.Path | Should -BeNullOrEmpty $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty diff --git a/tests/Integration/DSC_SqlServerAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 similarity index 78% rename from tests/Integration/DSC_SqlServerAudit.config.ps1 rename to tests/Integration/DSC_SqlAudit.config.ps1 index df9780dfc..c0d53fc22 100644 --- a/tests/Integration/DSC_SqlServerAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -26,43 +26,46 @@ else InstanceName = 'DSCSQLTEST' AuditName1 = 'FileAudit' - DestinationType1 = 'File' - FilePath1 = 'C:\Temp\audit\' + Path1 = 'C:\Temp\audit' MaximumFileSize1 = 10 MaximumFileSizeUnit1 = 'MB' MaximumRolloverFiles1 = 11 AuditName2 = 'SecLogAudit' - DestinationType2 = 'SecurityLog' + LogType2 = 'SecurityLog' Filter2 = '([server_principal_name] like ''%ADMINISTRATOR'')' } ) } + + # TODO: This leaves the SecLogAudit, if so it should be documented. + + # TODO: This folder should be created with DSC. + New-Item -Path 'C:\Temp\audit' -ItemType 'Directory' -Force | Out-Null } <# .SYNOPSIS Creates a Server Audit with File destination. #> -Configuration DSC_SqlServerAudit_AddFileAudit_Config +Configuration DSC_SqlAudit_AddFileAudit_Config { Import-DscResource -ModuleName 'SqlServerDsc' node $AllNodes.NodeName { - SqlServerAudit 'Integration_Test' + SqlAudit 'Integration_Test' { Ensure = 'Present' ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditName1 - DestinationType = $Node.DestinationType1 - FilePath = $Node.FilePath1 + Path = $Node.Path1 MaximumFileSize = $Node.MaximumFileSize1 MaximumFileSizeUnit = $Node.MaximumFileSizeUnit1 MaximumRolloverFiles = $Node.MaximumRolloverFiles1 - PsDscRunAsCredential = New-Object ` + Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } @@ -73,22 +76,22 @@ Configuration DSC_SqlServerAudit_AddFileAudit_Config .SYNOPSIS Creates a audit to the security log, with a filter. #> -Configuration DSC_SqlServerAudit_AddSecLogAudit_Config +Configuration DSC_SqlAudit_AddSecLogAudit_Config { Import-DscResource -ModuleName 'SqlServerDsc' node $AllNodes.NodeName { - SqlServerAudit 'Integration_Test' + SqlAudit 'Integration_Test' { Ensure = 'Present' ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditName2 - DestinationType = $Node.DestinationType2 - Filter = $Node.Filter2 + LogType = $Node.LogType2 + Filter = $Node.Filter2 - PsDscRunAsCredential = New-Object ` + Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } @@ -99,21 +102,21 @@ Configuration DSC_SqlServerAudit_AddSecLogAudit_Config .SYNOPSIS Should remove the filter #> -Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config +Configuration DSC_SqlAudit_AddSecLogAuditNoFilter_Config { Import-DscResource -ModuleName 'SqlServerDsc' node $AllNodes.NodeName { - SqlServerAudit 'Integration_Test' + SqlAudit 'Integration_Test' { Ensure = 'Present' ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditName2 - DestinationType = $Node.DestinationType2 + Filter = '' - PsDscRunAsCredential = New-Object ` + Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } @@ -124,20 +127,20 @@ Configuration DSC_SqlServerAudit_AddSecLogAuditNoFilter_Config .SYNOPSIS Removes the file audit. #> -Configuration DSC_SqlServerAudit_RemoveAudit1_Config +Configuration DSC_SqlAudit_RemoveAudit1_Config { Import-DscResource -ModuleName 'SqlServerDsc' node $AllNodes.NodeName { - SqlServerAudit 'Integration_Test' + SqlAudit 'Integration_Test' { Ensure = 'Absent' ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditName1 - PsDscRunAsCredential = New-Object ` + Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 index 47d7a7afc..82f8bedbc 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 @@ -1,4 +1,7 @@ BeforeDiscovery { + # TODO: THIS IS NOT RUN YET + return + try { Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' @@ -87,11 +90,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType1 - $resourceCurrentState.FilePath | Should -Be $ConfigurationData.AllNodes.FilePath1 - $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 - $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 - $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 } It 'Should return $true when Test-DscConfiguration is run' { @@ -149,67 +147,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 - $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_AddSecLogAuditNoFilter_Config" - ) { - BeforeAll { - $configurationName = $_ - } - - AfterAll { - Wait-ForIdleLcm - } - - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -Be $ConfigurationData.AllNodes.DestinationType2 - $resourceCurrentState.Filter | Should -BeNullOrEmpty } It 'Should return $true when Test-DscConfiguration is run' { @@ -267,11 +204,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.DestinationType | Should -BeNullOrEmpty - $resourceCurrentState.FilePath | Should -BeNullOrEmpty - $resourceCurrentState.MaximumFileSize | Should -BeNullOrEmpty - $resourceCurrentState.MaximumFileSizeUnit | Should -BeNullOrEmpty - $resourceCurrentState.MaximumRolloverFiles | Should -BeNullOrEmpty } It 'Should return $true when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 index 1190364ac..f2d879057 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -26,12 +26,13 @@ else InstanceName = 'DSCSQLTEST' AuditName1 = 'FileAudit' - DestinationType1 = 'File' - FilePath1 = 'C:\Temp\audit\' + Path1 = 'C:\Temp\audit\' MaximumFileSize1 = 10 MaximumFileSizeUnit1 = 'MB' MaximumRolloverFiles1 = 11 + LogType2 = 'SecurityLog' + AuditSpecificationName = 'AdminAudit' } ) @@ -48,14 +49,13 @@ Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config node $AllNodes.NodeName { - SqlServerAudit 'Integration_TestPrepare' + SqlAudit 'Integration_TestPrepare' { Ensure = 'Present' ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditName1 - DestinationType = $Node.DestinationType1 - FilePath = $Node.FilePath1 + Path = $Node.Path1 MaximumFileSize = $Node.MaximumFileSize1 MaximumFileSizeUnit = $Node.MaximumFileSizeUnit1 MaximumRolloverFiles = $Node.MaximumRolloverFiles1 @@ -96,7 +96,7 @@ Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config ServerRoleMemberChangeGroup = $true ServerStateChangeGroup = $true TraceChangeGroup = $true - DependsOn = '[SqlServerAudit]Integration_TestPrepare' + DependsOn = '[SqlAudit]Integration_TestPrepare' PsDscRunAsCredential = $SqlAdministratorCredential } @@ -113,75 +113,13 @@ Configuration DSC_SqlServerAuditSpecification_AddSecLogAudit_Config node $AllNodes.NodeName { - SqlServerAudit 'Integration_TestPrepare' - { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditName2 - DestinationType = $Node.DestinationType2 - Filter = $Node.Filter2 - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) - } - - SqlServerAuditSpecification 'Integration_Test' - { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlServerAudit]Integration_TestPrepare' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} - -<# - .SYNOPSIS - Should remove the filter -#> -Configuration DSC_SqlServerAuditSpecification_AddSecLogAuditNoFilter_Config -{ - Import-DscResource -ModuleName 'SqlServerDsc' - - node $AllNodes.NodeName - { - SqlServerAudit 'Integration_TestPrepare' + SqlAudit 'Integration_TestPrepare' { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditName2 - DestinationType = $Node.DestinationType2 + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName2 + LogType = $Node.LogType2 PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` @@ -219,7 +157,7 @@ Configuration DSC_SqlServerAuditSpecification_AddSecLogAuditNoFilter_Config ServerRoleMemberChangeGroup = $true ServerStateChangeGroup = $true TraceChangeGroup = $true - DependsOn = '[SqlServerAudit]Integration_TestPrepare' + DependsOn = '[SqlAudit]Integration_TestPrepare' PsDscRunAsCredential = $SqlAdministratorCredential } @@ -236,12 +174,12 @@ Configuration DSC_SqlServerAuditSpecification_RemoveAudit1_Config node $AllNodes.NodeName { - SqlServerAudit 'Integration_TestPrepare' + SqlAudit 'Integration_TestPrepare' { - Ensure = 'Absent' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditName1 + Ensure = 'Absent' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName1 PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` @@ -279,7 +217,7 @@ Configuration DSC_SqlServerAuditSpecification_RemoveAudit1_Config ServerRoleMemberChangeGroup = $true ServerStateChangeGroup = $true TraceChangeGroup = $true - DependsOn = '[SqlServerAudit]Integration_TestPrepare' + DependsOn = '[SqlAudit]Integration_TestPrepare' PsDscRunAsCredential = $SqlAdministratorCredential } From 1aca7d28f481df7bfbad6dea6d2d87628597a52b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 16:50:29 +0200 Subject: [PATCH 38/71] Fix example and integ tests --- .../Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 | 2 +- tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 | 4 ++-- tests/Integration/DSC_SqlAudit.config.ps1 | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 b/source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 index e2f58dda7..1847c2b0e 100644 --- a/source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 +++ b/source/Examples/Resources/SqlAudit/4-AddSecuritylogAuditWithFilter.ps1 @@ -25,7 +25,7 @@ Configuration Example Name = 'SecLogAudit' LogType = 'SecurityLog' Enabled = $true - Filter = '([server_principal_name] like ''%ADMINISTRATOR'')' + AuditFilter = '([server_principal_name] like ''%ADMINISTRATOR'')' Credential = $SqlAdministratorCredential } } diff --git a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 index 97a607b23..d21bcb047 100644 --- a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 @@ -153,7 +153,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.LogType | Should -Be $ConfigurationData.AllNodes.LogType2 - $resourceCurrentState.Filter | Should -Be $ConfigurationData.AllNodes.Filter2 + $resourceCurrentState.AuditFilter | Should -Be $ConfigurationData.AllNodes.AuditFilter2 } It 'Should return $true when Test-DscConfiguration is run' { @@ -212,7 +212,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.LogType | Should -Be $ConfigurationData.AllNodes.LogType2 - $resourceCurrentState.Filter | Should -BeNullOrEmpty + $resourceCurrentState.AuditFilter | Should -BeNullOrEmpty } It 'Should return $true when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_SqlAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 index c0d53fc22..51ea29b52 100644 --- a/tests/Integration/DSC_SqlAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -33,7 +33,7 @@ else AuditName2 = 'SecLogAudit' LogType2 = 'SecurityLog' - Filter2 = '([server_principal_name] like ''%ADMINISTRATOR'')' + AuditFilter2 = '([server_principal_name] like ''%ADMINISTRATOR'')' } ) } @@ -89,7 +89,7 @@ Configuration DSC_SqlAudit_AddSecLogAudit_Config InstanceName = $Node.InstanceName Name = $Node.AuditName2 LogType = $Node.LogType2 - Filter = $Node.Filter2 + AuditFilter = $Node.AuditFilter2 Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` @@ -114,7 +114,7 @@ Configuration DSC_SqlAudit_AddSecLogAuditNoFilter_Config ServerName = $Node.ServerName InstanceName = $Node.InstanceName Name = $Node.AuditName2 - Filter = '' + AuditFilter = '' Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` From eb63cd9e63ae573c569ee3226ff7235798d53fdb Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 17:05:18 +0200 Subject: [PATCH 39/71] FIx example and integ tests --- source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 | 2 +- tests/Integration/DSC_SqlAudit.config.ps1 | 9 +++++++-- .../DSC_SqlServerAuditSpecification.config.ps1 | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 b/source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 index 50df5debf..f0d3438e6 100644 --- a/source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 +++ b/source/Examples/Resources/SqlAudit/1-AddFileAudit.ps1 @@ -24,7 +24,7 @@ Configuration Example Name = 'FileAudit' Path = 'C:\Temp\audit' MaximumFileSize = 10 - MaximumFileSizeUnit = 'MB' + MaximumFileSizeUnit = 'Megabyte' MaximumRolloverFiles = 11 Enabled = $true Credential = $SqlAdministratorCredential diff --git a/tests/Integration/DSC_SqlAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 index 51ea29b52..293b42cdb 100644 --- a/tests/Integration/DSC_SqlAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -19,7 +19,12 @@ else NodeName = 'localhost' CertificateFile = $env:DscPublicCertificatePath - UserName = "$env:COMPUTERNAME\SqlAdmin" + <# + This must be either the UPN username (e.g. username@domain.local) + or the user name without the NetBIOS name (e.g. username). Using + the NetBIOS name (e.g. DOMAIN\username) will not work. + #> + UserName = "SqlAdmin" Password = 'P@ssw0rd1' ServerName = $env:COMPUTERNAME @@ -28,7 +33,7 @@ else AuditName1 = 'FileAudit' Path1 = 'C:\Temp\audit' MaximumFileSize1 = 10 - MaximumFileSizeUnit1 = 'MB' + MaximumFileSizeUnit1 = 'Megabyte' MaximumRolloverFiles1 = 11 AuditName2 = 'SecLogAudit' diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 index f2d879057..2e01b0cd7 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -28,7 +28,7 @@ else AuditName1 = 'FileAudit' Path1 = 'C:\Temp\audit\' MaximumFileSize1 = 10 - MaximumFileSizeUnit1 = 'MB' + MaximumFileSizeUnit1 = 'Megabyte' MaximumRolloverFiles1 = 11 LogType2 = 'SecurityLog' From 8bedb1a005e2f658c8625da216aa281a58792135 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 8 Aug 2022 18:35:09 +0200 Subject: [PATCH 40/71] Fix path --- tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 index d21bcb047..f89acd31d 100644 --- a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 @@ -91,7 +91,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.Path1 + $resourceCurrentState.Path | Should -Be 'C:\Temp\audit\' $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 From 0a0ff5af95c3ae0a009107f75f2c417727fbc601 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 08:04:40 +0200 Subject: [PATCH 41/71] Fix ConvertTo-Reason --- source/Private/ConvertTo-Reason.ps1 | 17 +++++++++++++- tests/Unit/Private/ConvertTo-Reason.Tests.ps1 | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 index eb5099206..658627360 100644 --- a/source/Private/ConvertTo-Reason.ps1 +++ b/source/Private/ConvertTo-Reason.ps1 @@ -89,10 +89,25 @@ function ConvertTo-Reason } } + # Convert the value to Json to be able to easily visualize complex types + $propertyActualValueJson = $propertyActualValue | ConvertTo-Json -Compress + $propertyExpectedValueJson = $propertyExpectedValue | ConvertTo-Json -Compress + + # If the property name contain the word Path, remove '\\' from path. + if ($currentProperty.Property -match 'Path') + { + $propertyActualValueJson = $propertyActualValueJson -replace '\\\\', '\' + $propertyExpectedValueJson = $propertyExpectedValueJson -replace '\\\\', '\' + } + $reasons += [Reason] @{ Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property # Convert the object to JSON to handle complex types. - Phrase = 'The property {0} should be {1}, but was {2}' -f $currentProperty.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) + Phrase = 'The property {0} should be {1}, but was {2}' -f @( + $currentProperty.Property, + $propertyExpectedValueJson, + $propertyActualValueJson + ) } } } diff --git a/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 index 89d8e6b4f..e2badb213 100644 --- a/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 +++ b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 @@ -168,6 +168,7 @@ Describe 'ConvertTo-Reason' -Tag 'Private' { AfterAll { $PSVersionTable.PSEdition = $script:originalPSEdition } + It 'Should return the correct values in a hashtable' { InModuleScope -ScriptBlock { $mockProperties = @( @@ -188,4 +189,25 @@ Describe 'ConvertTo-Reason' -Tag 'Private' { } } } + + Context 'When a path property contain double backslash' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourcePathProperty' + ExpectedValue = 'C:\Temp\MyFolder' + ActualValue = 'C:\Temp\MyNewFolder' + } + ) + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 1 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourcePathProperty' + $result.Phrase | Should -Contain 'The property MyResourcePathProperty should be "C:\Temp\MyFolder", but was "C:\Temp\MyNewFolder"' + } + } + } } From 96c1589973f0a94a2e1fbc2a636e3d896ba8ec59 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 11:06:13 +0200 Subject: [PATCH 42/71] Fix unit test SqlAudit --- source/Classes/020.SqlAudit.ps1 | 14 +- tests/Unit/Classes/SqlAudit.Tests.ps1 | 1017 +++++++++++++++++++++++++ 2 files changed, 1024 insertions(+), 7 deletions(-) create mode 100644 tests/Unit/Classes/SqlAudit.Tests.ps1 diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index 41709b74e..3e4a29767 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -354,15 +354,16 @@ class SqlAudit : ResourceBase $currentState.MaximumFiles = [System.UInt32] $auditObject.MaximumFiles $currentState.MaximumFileSize = [System.UInt32] $auditObject.MaximumFileSize - if ($auditObject.MaximumFileSizeUnit) + # The value of MaximumFileSizeUnit can be zero, so have to check against $null + if ($null -ne $auditObject.MaximumFileSizeUnit) { $convertedMaximumFileSizeUnit = ( @{ - Mb = 'Megabyte' - Gb = 'Gigabyte' - Tb = 'Terabyte' + 0 = 'Megabyte' + 1 = 'Gigabyte' + 2 = 'Terabyte' } - ).($auditObject.MaximumFileSizeUnit) + ).($auditObject.MaximumFileSizeUnit.value__) $currentState.MaximumFileSizeUnit = $convertedMaximumFileSizeUnit } @@ -405,7 +406,6 @@ class SqlAudit : ResourceBase # Remove properties that is not an audit property. 'InstanceName' 'ServerName' - 'Enabled' 'Ensure' 'Force' 'Credential' @@ -569,7 +569,7 @@ class SqlAudit : ResourceBase # Test so that the path exist. if ($properties.Keys -contains 'Path' -and -not (Test-Path -Path $properties.Path)) { - $errorMessage = $this.localizedData.PathInvalid + $errorMessage = $this.localizedData.PathInvalid -f $properties.Path New-InvalidArgumentException -ArgumentName 'Path' -Message $errorMessage } diff --git a/tests/Unit/Classes/SqlAudit.Tests.ps1 b/tests/Unit/Classes/SqlAudit.Tests.ps1 new file mode 100644 index 000000000..9ad838b27 --- /dev/null +++ b/tests/Unit/Classes/SqlAudit.Tests.ps1 @@ -0,0 +1,1017 @@ +<# + .SYNOPSIS + Unit test for DSC_SqlAudit DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + # Load the correct SQL Module stub + $script:stubModuleName = Import-SQLModuleStub -PassThru + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Unload the stub module. + Remove-SqlModuleStub -Name $script:stubModuleName + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe 'SqlAudit' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [SqlAudit]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + $instance = [SqlAudit]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [SqlAudit]::new() + $instance.GetType().Name | Should -Be 'SqlAudit' + } + } + } +} + +Describe 'SqlAudit\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When having a File audit with default values' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlAuditInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlAuditInstance.Get() + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.Name | Should -Be 'MockAuditName' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Reasons | Should -BeNullOrEmpty + + $currentState.Path | Should -Be 'C:\Temp' + } + } + + Context 'When using parameter Credential' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlAuditInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + Credential = $this.Credential + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlAuditInstance.Get() + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.Name | Should -Be 'MockAuditName' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Reasons | Should -BeNullOrEmpty + + $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] + $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' + + $currentState.Path | Should -Be 'C:\Temp' + } + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When property Path have the wrong value for a File audit' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\NewFolder' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlAuditInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlAuditInstance.Get() + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.Name | Should -Be 'MockAuditName' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Path | Should -Be 'C:\Temp' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'SqlAudit:SqlAudit:Path' + $currentState.Reasons[0].Phrase | Should -Be 'The property Path should be "C:\NewFolder", but was "C:\Temp"' + } + } + } + } +} + +Describe 'SqlAudit\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } | + # Mock method Modify which is called by the base method Set(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { + $script:mockMethodModifyCallCount += 1 + } -PassThru + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodModifyCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'Path' + ExpectedValue = 'C:\NewFolder' + ActualValue = 'C:\Path' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'SqlAudit\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\WrongFolder' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'SqlAudit\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When audit is missing in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlAuditInstance.GetCurrentState( + @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } + ) + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Force | Should -BeFalse + $currentState.Credential | Should -BeNullOrEmpty + } + } + + Context 'When using property Credential' { + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + + $currentState = $script:mockSqlAuditInstance.GetCurrentState( + @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } + ) + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Force | Should -BeFalse + + $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] + $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' + } + } + } + } + + Context 'When the audit is present in the current state' { + Context 'When the audit is of type file' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + + <# + All file properties is set to a value in this test regardless + that they can not all be set at the same time in a real scenario, + e.g. MaximumFiles and MaximumRolloverFiles that are not allowed + to be set to a non-zero value at the same time. + #> + $mockAuditObject.DestinationType = 'File' + $mockAuditObject.FilePath = 'C:\Temp' + $mockAuditObject.Filter = '([server_principal_name] like ''%ADMINISTRATOR'')' + $mockAuditObject.MaximumFiles = 2 + $mockAuditObject.MaximumFileSize = 2 + $mockAuditObject.MaximumFileSizeUnit = [Microsoft.SqlServer.Management.Smo.AuditFileSizeUnit]::Mb + $mockAuditObject.MaximumRolloverFiles = 2 + $mockAuditObject.OnFailure = 'Continue' + $mockAuditObject.QueueDelay = 1000 + $mockAuditObject.Guid = '06962963-ddd1-4a6b-86d6-0ef8d99b8e7b' + $mockAuditObject.ReserveDiskSpace = $true + $mockAuditObject.Enabled = $true + + return $mockAuditObject + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlAuditInstance.GetCurrentState( + @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } + ) + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Force | Should -BeFalse + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.LogType | Should -BeNullOrEmpty + $currentState.Path | Should -Be 'C:\Temp' + $currentState.AuditFilter | Should -Be '([server_principal_name] like ''%ADMINISTRATOR'')' + $currentState.MaximumFiles | Should -Be 2 + $currentState.MaximumFileSize | Should -Be 2 + $currentState.MaximumFileSizeUnit | Should -Be 'Megabyte' + $currentState.MaximumRolloverFiles | Should -Be 2 + $currentState.OnFailure | Should -Be 'Continue' + $currentState.QueueDelay | Should -Be 1000 + $currentState.AuditGuid | Should -Be '06962963-ddd1-4a6b-86d6-0ef8d99b8e7b' + $currentState.ReserveDiskSpace | Should -BeTrue + $currentState.Enabled | Should -BeTrue + } + } + } + } +} + +Describe 'SqlAudit\Modify()' -Tag 'Modify' { + Context 'When the system is not in the desired state' { + Context 'When audit is present but should be absent' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Ensure = 'Absent' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Remove-SqlDscAudit + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Ensure = 'Absent' + Path = 'C:\Temp' + } + ) + + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 1 -Scope It + } + } + } + + Context 'When audit is absent but should be present' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName New-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } -RemoveParameterValidation 'Path' + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Ensure = 'Present' + Path = 'C:\Temp' + } + ) + + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 1 -Scope It + } + } + + Context 'When the audit should also be enabled' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Enable-SqlDscAudit + } + + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Ensure = 'Present' + Path = 'C:\Temp' + Enabled = $true + } + ) + + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Enable-SqlDscAudit -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the audit should also be disabled' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Disable-SqlDscAudit + } + + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Ensure = 'Present' + Path = 'C:\Temp' + Enabled = $false + } + ) + + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Disable-SqlDscAudit -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the neither of the parameters LogType or Path was passed' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Disable-SqlDscAudit + } + + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + $mockErrorMessage = Get-InvalidOperationRecord -Message $mockSqlAuditInstance.localizedData.CannotCreateNewAudit + + { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Ensure = 'Present' + Enabled = $false + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + } + + Context 'When audit should be enabled but is disabled' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Enabled = $false + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } + + Mock -CommandName Enable-SqlDscAudit + } + + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Enabled = $true + } + ) + + Should -Invoke -CommandName Get-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Enable-SqlDscAudit -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the audit should be disabled but is enabled' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Enabled = $true + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } + + Mock -CommandName Disable-SqlDscAudit + } + + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Enabled = $false + } + ) + + Should -Invoke -CommandName Get-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Disable-SqlDscAudit -Exactly -Times 1 -Scope It + } + } + } + } +} + +Describe 'SqlAudit\AssertProperties()' -Tag 'AssertProperties' { + Context 'When the path does not exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + + Mock -CommandName Test-Path -MockWith { + return $false + } + } + + It 'Should throw the correct error for Get()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.PathInvalid -f 'C:\Temp' + + $mockErrorMessage += ' (Parameter ''Path'')' + + { $script:mockSqlAuditInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Set()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.PathInvalid -f 'C:\Temp' + + $mockErrorMessage += ' (Parameter ''Path'')' + + { $script:mockSqlAuditInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Test()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.PathInvalid -f 'C:\Temp' + + $mockErrorMessage += ' (Parameter ''Path'')' + + { $script:mockSqlAuditInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + <# + These tests just check for the string localized ID. Since the error is part + of a command outside of SqlServerDsc, a small changes to the localized + string should not fail these tests. + #> + Context 'When passing mutually exclusive parameters' { + Context 'When passing MaximumFiles and MaximumRolloverFiles' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + } + + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockSqlAuditInstance.AssertProperties( + @{ + MaximumFiles = 2 + MaximumRolloverFiles = 2 + } + ) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + + Context 'When passing LogType and a File audit property' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } + } + } + + It 'Should throw the correct error for property ''''' -ForEach @( + @{ + MockPropertyName = 'Path' + } + @{ + MockPropertyName = 'MaximumFiles' + } + @{ + MockPropertyName = 'MaximumFileSize' + } + @{ + MockPropertyName = 'MaximumFileSizeUnit' + } + @{ + MockPropertyName = 'MaximumRolloverFiles' + } + @{ + MockPropertyName = 'ReserveDiskSpace' + } + ) { + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlAuditInstance.AssertProperties( + @{ + LogType = 'SecurityLog' + $MockPropertyName = 'AnyValue' + } + ) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + + Context 'When passing just one of either MaximumFileSize and MaximumFileSizeUnit' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + } + + It 'Should throw the correct error for property ''''' -ForEach @( + @{ + MockPropertyName = 'MaximumFileSize' + } + @{ + MockPropertyName = 'MaximumFileSizeUnit' + } + ) { + InModuleScope -Parameters $_ -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.BothFileSizePropertiesMustBeSet + + $mockErrorMessage += ' (Parameter ''MaximumFileSize, MaximumFileSizeUnit'')' + + { + $mockSqlAuditInstance.AssertProperties( + @{ + $MockPropertyName = 'AnyValue' + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When passing MaximumFileSize with a value of 1' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + } + + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.MaximumFileSizeValueInvalid + + $mockErrorMessage += ' (Parameter ''MaximumFileSize'')' + + { + $mockSqlAuditInstance.AssertProperties( + @{ + MaximumFileSize = 1 + MaximumFileSizeUnit = 'Megabyte' + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When passing QueueDelay with an invalid value' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + } + + It 'Should throw the correct error with value ' -ForEach @( + @{ + MockQueueDelayValue = 1 + } + @{ + MockQueueDelayValue = 457 + } + @{ + MockQueueDelayValue = 800 + } + @{ + MockQueueDelayValue = 999 + } + ) { + InModuleScope -Parameters $_ -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.QueueDelayValueInvalid + + $mockErrorMessage += ' (Parameter ''QueueDelay'')' + + { + $mockSqlAuditInstance.AssertProperties( + @{ + QueueDelay = $MockQueueDelayValue + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When passing ReserveDiskSpace without passing MaximumFiles' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } + } + } + + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockSqlAuditInstance.localizedData.BothFileSizePropertiesMustBeSet + + $mockErrorMessage += ' (Parameter ''ReserveDiskSpace'')' + + { + $mockSqlAuditInstance.AssertProperties( + @{ + ReserveDiskSpace = $true + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + } +} From a6244567ad3d06572e93a0e5e2084fcde16c841f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 11:41:15 +0200 Subject: [PATCH 43/71] Fix trimming of path --- source/Classes/020.SqlAudit.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index 3e4a29767..61a2fec48 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -349,7 +349,12 @@ class SqlAudit : ResourceBase $currentState.LogType = $auditObject.DestinationType } - $currentState.Path = $auditObject.FilePath + if ($auditObject.FilePath) + { + # Remove trailing slash or backslash. + $currentState.Path = $auditObject.FilePath -replace '[\\|/]*$' + } + $currentState.AuditFilter = $auditObject.Filter $currentState.MaximumFiles = [System.UInt32] $auditObject.MaximumFiles $currentState.MaximumFileSize = [System.UInt32] $auditObject.MaximumFileSize From 4f178b57227668055203ca2009a6a1772f50c471 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 12:07:22 +0200 Subject: [PATCH 44/71] Fix Set for SqlAudit --- source/Classes/020.SqlAudit.ps1 | 30 +++++++++++++++---- .../DSC_SqlAudit.Integration.Tests.ps1 | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index 61a2fec48..e06891972 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -405,7 +405,6 @@ class SqlAudit : ResourceBase # Get all properties that has an assigned value. $assignedDscProperties = $this | Get-DscProperty -HasValue -Type @( 'Key' - 'Mandatory' 'Optional' ) -ExcludeName @( # Remove properties that is not an audit property. @@ -453,13 +452,34 @@ class SqlAudit : ResourceBase if ($auditObject) { - } + # TODO: Should evaluate if Path is assigned and DestinationType is *Log it should recreate if Force is $true - # TODO: Should evaluate if Path is assigned and DestinationType is *Log it should recreate if Force is $true + # TODO: Should evaluate if LogType is assigned and DestinationType is File it should recreate if Force is $true - # TODO: Should evaluate if LogType is assigned and DestinationType is File it should recreate if Force is $true + # TODO: Should evaluate DestinationType so that is does not try to set File properties when type is Log (and LogType and Path is not also present) - # TODO: Should evaluate DestinationType so that is does not try to set File properties when type is Log (and LogType and Path is not also present) + # Get all optional properties that has an assigned value. + $assignedOptionalDscProperties = $this | Get-DscProperty -HasValue -Type 'Optional' -ExcludeName @( + # Remove optional properties that is not an audit property. + 'ServerName' + 'Ensure' + 'Force' + 'Credential' + + # Remove this audit property since it must be handled later. + 'Enabled' + ) + + <# + Only call Set when there is a property to Set. The property + Enabled was ignored, so it could be the only one that was + not in desired state (Enabled is handled later). + #> + if ($assignedOptionalDscProperties.Count -gt 0) + { + $auditObject | Set-SqlDscAudit @assignedOptionalDscProperties -Force + } + } } } diff --git a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 index f89acd31d..d21bcb047 100644 --- a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 @@ -91,7 +91,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - $resourceCurrentState.Path | Should -Be 'C:\Temp\audit\' + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.Path1 $resourceCurrentState.MaximumFileSize | Should -Be $ConfigurationData.AllNodes.MaximumFileSize1 $resourceCurrentState.MaximumFileSizeUnit | Should -Be $ConfigurationData.AllNodes.MaximumFileSizeUnit1 $resourceCurrentState.MaximumRolloverFiles | Should -Be $ConfigurationData.AllNodes.MaximumRolloverFiles1 From e1fecfba8f89fc755846b7986bab3ff5c7e6e474 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 12:46:50 +0200 Subject: [PATCH 45/71] Test using new parent class SqlResourceBase --- source/Classes/011.SqlResourceBase.ps1 | 44 +++++++++++++++ source/Classes/020.SqlAudit.ps1 | 42 +++++++------- source/en-US/SqlResourceBase.strings.psd1 | 8 +++ tests/Unit/Classes/SqlAudit.Tests.ps1 | 67 ++++++++++++++++++++++- 4 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 source/Classes/011.SqlResourceBase.ps1 create mode 100644 source/en-US/SqlResourceBase.strings.psd1 diff --git a/source/Classes/011.SqlResourceBase.ps1 b/source/Classes/011.SqlResourceBase.ps1 new file mode 100644 index 000000000..6fe8cb542 --- /dev/null +++ b/source/Classes/011.SqlResourceBase.ps1 @@ -0,0 +1,44 @@ +<# + .SYNOPSIS + The SqlResource base have... +#> +class SqlResourceBase : ResourceBase +{ + <# + Property for holding the server connection object. + This should be an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] $SqlServerObject = $null + + SqlResourceBase() : base () {} + + <# + Returns and reuses the server connection object. If the server connection + object does not exist a connection to the SQL Server instance will occur. + + This should return an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] GetServerObject() + { + if (-not $this.SqlServerObject) + { + $connectSqlDscDatabaseEngineParameters = @{ + ServerName = $this.ServerName + InstanceName = $this.InstanceName + } + + if ($this.Credential) + { + $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + } + + $this.SqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + } + + return $this.SqlServerObject + } +} diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index e06891972..f84c98ad8 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -134,7 +134,7 @@ #> [DscResource(RunAsCredential = 'Optional')] -class SqlAudit : ResourceBase +class SqlAudit : SqlResourceBase { <# Property for holding the server connection object. @@ -142,7 +142,7 @@ class SqlAudit : ResourceBase but using that type fails the build process currently. See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. #> - hidden [System.Object] $sqlServerObject = $null + #hidden [System.Object] $sqlServerObject = $null [DscProperty(Key)] [System.String] @@ -276,25 +276,25 @@ class SqlAudit : ResourceBase but using that type fails the build process currently. See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. #> - hidden [System.Object] GetServerObject() - { - if (-not $this.sqlServerObject) - { - $connectSqlDscDatabaseEngineParameters = @{ - ServerName = $this.ServerName - InstanceName = $this.InstanceName - } - - if ($this.Credential) - { - $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential - } - - $this.sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters - } - - return $this.sqlServerObject - } + # hidden [System.Object] GetServerObject() + # { + # if (-not $this.sqlServerObject) + # { + # $connectSqlDscDatabaseEngineParameters = @{ + # ServerName = $this.ServerName + # InstanceName = $this.InstanceName + # } + + # if ($this.Credential) + # { + # $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + # } + + # $this.sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + # } + + # return $this.sqlServerObject + # } <# Base method Get() call this method to get the current state as a hashtable. diff --git a/source/en-US/SqlResourceBase.strings.psd1 b/source/en-US/SqlResourceBase.strings.psd1 new file mode 100644 index 000000000..6c943bb14 --- /dev/null +++ b/source/en-US/SqlResourceBase.strings.psd1 @@ -0,0 +1,8 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + class SqlResourceBase. +#> + +ConvertFrom-StringData @' +'@ diff --git a/tests/Unit/Classes/SqlAudit.Tests.ps1 b/tests/Unit/Classes/SqlAudit.Tests.ps1 index 9ad838b27..9c46b3008 100644 --- a/tests/Unit/Classes/SqlAudit.Tests.ps1 +++ b/tests/Unit/Classes/SqlAudit.Tests.ps1 @@ -483,10 +483,75 @@ Describe 'SqlAudit\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.MaximumFileSize | Should -Be 2 $currentState.MaximumFileSizeUnit | Should -Be 'Megabyte' $currentState.MaximumRolloverFiles | Should -Be 2 + $currentState.ReserveDiskSpace | Should -BeTrue + $currentState.OnFailure | Should -Be 'Continue' + $currentState.QueueDelay | Should -Be 1000 + $currentState.AuditGuid | Should -Be '06962963-ddd1-4a6b-86d6-0ef8d99b8e7b' + $currentState.Enabled | Should -BeTrue + } + } + } + + Context 'When the audit is of type log' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + + <# + All file properties is set to a value in this test regardless + that they can not all be set at the same time in a real scenario, + e.g. MaximumFiles and MaximumRolloverFiles that are not allowed + to be set to a non-zero value at the same time. + #> + $mockAuditObject.DestinationType = 'SecurityLog' + $mockAuditObject.Filter = '([server_principal_name] like ''%ADMINISTRATOR'')' + $mockAuditObject.OnFailure = 'Continue' + $mockAuditObject.QueueDelay = 1000 + $mockAuditObject.Guid = '06962963-ddd1-4a6b-86d6-0ef8d99b8e7b' + $mockAuditObject.Enabled = $true + + return $mockAuditObject + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlAuditInstance.GetCurrentState( + @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + } + ) + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Force | Should -BeFalse + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.LogType | Should -Be 'SecurityLog' + $currentState.Path | Should -BeNullOrEmpty + $currentState.AuditFilter | Should -Be '([server_principal_name] like ''%ADMINISTRATOR'')' + $currentState.MaximumFiles | Should -Be 0 + $currentState.MaximumFileSize | Should -Be 0 + $currentState.MaximumFileSizeUnit | Should -BeNullOrEmpty + $currentState.MaximumRolloverFiles | Should -Be 0 + $currentState.ReserveDiskSpace | Should -BeNullOrEmpty $currentState.OnFailure | Should -Be 'Continue' $currentState.QueueDelay | Should -Be 1000 $currentState.AuditGuid | Should -Be '06962963-ddd1-4a6b-86d6-0ef8d99b8e7b' - $currentState.ReserveDiskSpace | Should -BeTrue $currentState.Enabled | Should -BeTrue } } From 75095db83b4f95773f9bd01e85578752153f7731 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 12:59:16 +0200 Subject: [PATCH 46/71] DEBUG1 --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 40e6d57a3..38b711f17 100644 --- a/build.yaml +++ b/build.yaml @@ -13,7 +13,7 @@ BuildWorkflow: - Build_NestedModules_ModuleBuilder - Create_Changelog_Release_Output - Generate_Conceptual_Help - - Generate_Wiki_Content + #- Generate_Wiki_Content pack: - build From 9eabe154d84f183b74f20e35fff7e8dc2726b837 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 13:36:18 +0200 Subject: [PATCH 47/71] Revert build.yaml --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 38b711f17..40e6d57a3 100644 --- a/build.yaml +++ b/build.yaml @@ -13,7 +13,7 @@ BuildWorkflow: - Build_NestedModules_ModuleBuilder - Create_Changelog_Release_Output - Generate_Conceptual_Help - #- Generate_Wiki_Content + - Generate_Wiki_Content pack: - build From 273ca2b1ed476fb4646dc946b08350fa6644ecf0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 14:33:56 +0200 Subject: [PATCH 48/71] Fix SqlResourceBase --- source/Classes/011.SqlResourceBase.ps1 | 10 ++++-- source/Classes/020.SqlAudit.ps1 | 47 ++------------------------ 2 files changed, 9 insertions(+), 48 deletions(-) diff --git a/source/Classes/011.SqlResourceBase.ps1 b/source/Classes/011.SqlResourceBase.ps1 index 6fe8cb542..50af82558 100644 --- a/source/Classes/011.SqlResourceBase.ps1 +++ b/source/Classes/011.SqlResourceBase.ps1 @@ -1,6 +1,7 @@ <# .SYNOPSIS - The SqlResource base have... + The SqlResource base have generic properties and methods for the class-based + resources. #> class SqlResourceBase : ResourceBase { @@ -10,9 +11,12 @@ class SqlResourceBase : ResourceBase but using that type fails the build process currently. See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. #> - hidden [System.Object] $SqlServerObject = $null + hidden [System.Object] $SqlServerObject - SqlResourceBase() : base () {} + SqlResourceBase () : base () + { + $this.SqlServerObject = $null + } <# Returns and reuses the server connection object. If the server connection diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index f84c98ad8..c066cc8b8 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -136,14 +136,6 @@ [DscResource(RunAsCredential = 'Optional')] class SqlAudit : SqlResourceBase { - <# - Property for holding the server connection object. - This should be an object of type [Microsoft.SqlServer.Management.Smo.Server] - but using that type fails the build process currently. - See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. - #> - #hidden [System.Object] $sqlServerObject = $null - [DscProperty(Key)] [System.String] $InstanceName @@ -161,7 +153,7 @@ class SqlAudit : SqlResourceBase [System.String] $LogType - # TODO: Must assert the path at run time + # The Path is evaluated if exist in AssertProperties(). [DscProperty()] [System.String] $Path @@ -230,7 +222,7 @@ class SqlAudit : SqlResourceBase [Reason[]] $Reasons - SqlAudit() : base () + SqlAudit () : base () { # TODO:_Rename this to ExcludeDscProperties or ExcludeProperties # These properties will not be enforced. @@ -261,41 +253,6 @@ class SqlAudit : SqlResourceBase ([ResourceBase] $this).Set() } - <# - TODO: This method can be moved to a parent class "SqlServerDscResource" that - instead inherits ResourceBase. Then this method does not need to be - duplicated. Make sure to create a localized strings file for the new - class. - The property 'sqlServerObject' should also be moved (but still be hidden). - #> - <# - Returns and reuses the server connection object. If the server connection - object does not exist a connection to the SQL Server instance will occur. - - This should return an object of type [Microsoft.SqlServer.Management.Smo.Server] - but using that type fails the build process currently. - See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. - #> - # hidden [System.Object] GetServerObject() - # { - # if (-not $this.sqlServerObject) - # { - # $connectSqlDscDatabaseEngineParameters = @{ - # ServerName = $this.ServerName - # InstanceName = $this.InstanceName - # } - - # if ($this.Credential) - # { - # $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential - # } - - # $this.sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters - # } - - # return $this.sqlServerObject - # } - <# Base method Get() call this method to get the current state as a hashtable. The parameter properties will contain the key properties. From 622520884fa012ca5a8a7cd3747d8ab500dccca5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 15:41:08 +0200 Subject: [PATCH 49/71] Add more tests --- source/Classes/020.SqlAudit.ps1 | 6 + tests/Unit/Classes/SqlAudit.Tests.ps1 | 204 ++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index c066cc8b8..fa5bf544c 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -434,6 +434,12 @@ class SqlAudit : SqlResourceBase #> if ($assignedOptionalDscProperties.Count -gt 0) { + <# + This calls Set-SqlDscAudit to set all the desired value + even if they were in desired state. Then the no logic is + needed to make sure we call using the correct parameter + set that Set-SqlDscAudit requires. + #> $auditObject | Set-SqlDscAudit @assignedOptionalDscProperties -Force } } diff --git a/tests/Unit/Classes/SqlAudit.Tests.ps1 b/tests/Unit/Classes/SqlAudit.Tests.ps1 index 9c46b3008..fed88d7b1 100644 --- a/tests/Unit/Classes/SqlAudit.Tests.ps1 +++ b/tests/Unit/Classes/SqlAudit.Tests.ps1 @@ -822,6 +822,210 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { } } } + + Context 'When the property is not in desired state' -ForEach @( + @{ + MockPropertyName = 'Path' + MockExpectedValue = 'C:\NewValue' + } + @{ + MockPropertyName = 'AuditFilter' + MockExpectedValue = 'object -like ''something''' + } + @{ + MockPropertyName = 'MaximumFiles' + MockExpectedValue = 2 + } + @{ + MockPropertyName = 'MaximumRolloverFiles' + MockExpectedValue = 2 + } + @{ + MockPropertyName = 'OnFailure' + MockExpectedValue = 'FailOperation' + } + @{ + MockPropertyName = 'QueueDelay' + MockExpectedValue = 2000 + } + @{ + MockPropertyName = 'AuditGuid' + MockExpectedValue = 'cfa0d47e-bf93-41ab-bc9a-b8511acbcdd6' + } + ) { + BeforeAll { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + $MockPropertyName = $MockExpectedValue + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } + + Mock -CommandName Set-SqlDscAudit -RemoveParameterValidation 'Path' + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + $MockPropertyName = $MockExpectedValue + } + ) + + Should -Invoke -CommandName Get-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Set-SqlDscAudit -ParameterFilter { + $PesterBoundParameters.$MockPropertyName -eq $MockExpectedValue + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the property MaximumFileSize is not in desired state' { + BeforeAll { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + MaximumFileSize = 20 + MaximumFileSizeUnit = 'Megabyte' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } + + Mock -CommandName Set-SqlDscAudit -RemoveParameterValidation 'Path' + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + MaximumFileSize = 20 + } + ) + + Should -Invoke -CommandName Get-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Set-SqlDscAudit -ParameterFilter { + $MaximumFileSize -eq 20 + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the property MaximumFileSizeUnit is not in desired state' { + BeforeAll { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + MaximumFileSize = 20 + MaximumFileSizeUnit = 'Megabyte' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } + + Mock -CommandName Set-SqlDscAudit -RemoveParameterValidation 'Path' + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + MaximumFileSizeUnit = 'Megabyte' + } + ) + + Should -Invoke -CommandName Get-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Set-SqlDscAudit -ParameterFilter { + $MaximumFileSizeUnit -eq 'Megabyte' + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the property ReservDiskSpace is not in desired state' { + BeforeAll { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + MaximumFiles = 20 + ReserveDiskSpace = $true + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + } + + Mock -CommandName Set-SqlDscAudit -RemoveParameterValidation 'Path' + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + MaximumFileSizeUnit = 'Megabyte' + } + ) + + Should -Invoke -CommandName Get-SqlDscAudit -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Set-SqlDscAudit -ParameterFilter { + $ReserveDiskSpace -eq $true + } -Exactly -Times 1 -Scope It + } + } + } } } From 1353640b595b5a391341b6b4564a44d567e5f423 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 15:44:56 +0200 Subject: [PATCH 50/71] Enable more tests --- .../DSC_SqlServerAuditSpecification.Integration.Tests.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 index 82f8bedbc..970d3d934 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 @@ -1,7 +1,4 @@ BeforeDiscovery { - # TODO: THIS IS NOT RUN YET - return - try { Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' From faccd388f3abb4e19c786429b2bcb6cf304c74da Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 16:00:11 +0200 Subject: [PATCH 51/71] Move generic properties to SqlResourceBase --- source/Classes/011.SqlResourceBase.ps1 | 33 +++++++++++++++++++++++++ source/Classes/020.SqlAudit.ps1 | 34 -------------------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/source/Classes/011.SqlResourceBase.ps1 b/source/Classes/011.SqlResourceBase.ps1 index 50af82558..dde29020c 100644 --- a/source/Classes/011.SqlResourceBase.ps1 +++ b/source/Classes/011.SqlResourceBase.ps1 @@ -2,6 +2,23 @@ .SYNOPSIS The SqlResource base have generic properties and methods for the class-based resources. + + .PARAMETER InstanceName + The name of the _SQL Server_ instance to be configured. Default value is + `'MSSQLSERVER'`. + + .PARAMETER ServerName + The host name of the _SQL Server_ to be configured. Default value is the + current computer name. + + .PARAMETER Credential + Specifies the credential to use to connect to the _SQL Server_ instance. + + If parameter **Credential'* is not provided then the resource instance is + run using the credential that runs the configuration. + + .PARAMETER Reasons + Returns the reason a property is not in desired state. #> class SqlResourceBase : ResourceBase { @@ -13,6 +30,22 @@ class SqlResourceBase : ResourceBase #> hidden [System.Object] $SqlServerObject + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty()] + [PSCredential] + $Credential + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + SqlResourceBase () : base () { $this.SqlServerObject = $null diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index fa5bf544c..b34daf6d6 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -44,17 +44,9 @@ See more information in [Credential Overview](https://github.com/dsccommunity/SqlServerDsc/wiki/CredentialOverview). - .PARAMETER InstanceName - The name of the _SQL Server_ instance to be configured. Default value is - `'MSSQLSERVER'`. - .PARAMETER Name The name of the audit. - .PARAMETER ServerName - The host name of the _SQL Server_ to be configured. Default value is the - current computer name. - .PARAMETER LogType Specifies the to which log an audit logs to. Mutually exclusive to parameter **Path**. @@ -113,15 +105,6 @@ exist with the same name but of a different audit type. Defaults to `$false` not allowing server audits to be re-created. - .PARAMETER Credential - Specifies the credential to use to connect to the _SQL Server_ instance. - - If parameter **Credential'* is not provided then the resource instance is - run using the credential that runs the configuration. - - .PARAMETER Reasons - Returns the reason a property is not in desired state. - .EXAMPLE Invoke-DscResource -ModuleName SqlServerDsc -Name SqlAudit -Method Get -Property @{ ServerName = 'localhost' @@ -132,22 +115,13 @@ This example shows how to call the resource using Invoke-DscResource. #> - [DscResource(RunAsCredential = 'Optional')] class SqlAudit : SqlResourceBase { - [DscProperty(Key)] - [System.String] - $InstanceName - [DscProperty(Key)] [System.String] $Name - [DscProperty()] - [System.String] - $ServerName = (Get-ComputerName) - [DscProperty()] [ValidateSet('SecurityLog', 'ApplicationLog')] [System.String] @@ -214,14 +188,6 @@ class SqlAudit : SqlResourceBase [Nullable[System.Boolean]] $Force - [DscProperty()] - [PSCredential] - $Credential - - [DscProperty(NotConfigurable)] - [Reason[]] - $Reasons - SqlAudit () : base () { # TODO:_Rename this to ExcludeDscProperties or ExcludeProperties From a0a94d64d56cee02be9fac25758b1f382cd42bfa Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 19:28:31 +0200 Subject: [PATCH 52/71] Fix integ test --- .../DSC_SqlServerAuditSpecification.psm1 | 19 ++++++++++++++++--- ...erAuditSpecification.Integration.Tests.ps1 | 6 +++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 index 8114668e8..e9f2ab918 100644 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 @@ -571,7 +571,7 @@ function Set-TargetResource $auditSpecificationAddDropString = Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues - Write-Verbose -Message $( + Write-Debug -Message $( Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues ) @@ -1090,7 +1090,7 @@ function Test-TargetResource $testDscParameterStateParameters = @{ CurrentValues = $getTargetResourceResult DesiredValues = $desiredValues - ValuesToCheck = @( + Properties = @( 'Ensure' 'AuditName' 'ApplicationRoleChangePasswordGroup' @@ -1137,8 +1137,21 @@ function Test-TargetResource 'TransactionGroup' 'Enabled' ) + TurnOffTypeChecking = $true + # TODO: Remove verbose + Verbose = $true + } + + $propertiesNotInDesiredState = Compare-ResourcePropertyState @testDscParameterStateParameters + + if ($propertiesNotInDesiredState) + { + $testTargetResourceReturnValue = $false + } + else + { + $testTargetResourceReturnValue = $true } - $testTargetResourceReturnValue = Test-DscParameterState @testDscParameterStateParameters } else { diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 index 970d3d934..4bd4c7f66 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 @@ -84,7 +84,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName } @@ -141,7 +141,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName } @@ -198,7 +198,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } $resourceCurrentState.Ensure | Should -Be 'Absent' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName1 + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName } From c7c9a288e573309d4c81ff7c0fbd5879c8faab40 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 19:29:42 +0200 Subject: [PATCH 53/71] Debug appveyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 83042069c..08dd5999f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -79,7 +79,7 @@ test_script: #'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' #'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' - #'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' ## Group 4 #'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' From 22e017feb0aa6a73763275ea84f10e6e3e2c0bd3 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 20:18:45 +0200 Subject: [PATCH 54/71] Fix Test --- .../DSC_SqlServerAuditSpecification.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 index e9f2ab918..ee0ab55f0 100644 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 +++ b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 @@ -1142,7 +1142,7 @@ function Test-TargetResource Verbose = $true } - $propertiesNotInDesiredState = Compare-ResourcePropertyState @testDscParameterStateParameters + $propertiesNotInDesiredState = Compare-DscParameterState @testDscParameterStateParameters if ($propertiesNotInDesiredState) { From 90dcc2e4e0c0f7f55326ce2675b343088afc074f Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 20:45:06 +0200 Subject: [PATCH 55/71] Fix integ test --- ...SqlServerAuditSpecification.Integration.Tests.ps1 | 6 +++--- .../DSC_SqlServerAuditSpecification.config.ps1 | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 index 4bd4c7f66..dcd0c8dd3 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 @@ -84,7 +84,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName } @@ -141,7 +141,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName2 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName } @@ -198,7 +198,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } $resourceCurrentState.Ensure | Should -Be 'Absent' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName1 $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName } diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 index 2e01b0cd7..8efe87907 100644 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 @@ -31,9 +31,11 @@ else MaximumFileSizeUnit1 = 'Megabyte' MaximumRolloverFiles1 = 11 + AuditName2 = 'SecLogAudit' LogType2 = 'SecurityLog' - AuditSpecificationName = 'AdminAudit' + AuditSpecificationName1 = 'AdminAudit1' + AuditSpecificationName2 = 'AdminAudit2' } ) } @@ -70,7 +72,7 @@ Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config Ensure = 'Present' ServerName = $Node.ServerName InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName + Name = $Node.AuditSpecificationName1 AuditName = $Node.AuditName1 Enabled = $true AuditChangeGroup = $true @@ -131,8 +133,8 @@ Configuration DSC_SqlServerAuditSpecification_AddSecLogAudit_Config Ensure = 'Present' ServerName = $Node.ServerName InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName - AuditName = $Node.AuditName1 + Name = $Node.AuditSpecificationName2 + AuditName = $Node.AuditName2 Enabled = $true AuditChangeGroup = $true BackupRestoreGroup = $true @@ -191,7 +193,7 @@ Configuration DSC_SqlServerAuditSpecification_RemoveAudit1_Config Ensure = 'Absent' ServerName = $Node.ServerName InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName + Name = $Node.AuditSpecificationName1 AuditName = $Node.AuditName1 Enabled = $true AuditChangeGroup = $true From 8d3973cd0b7b96d8233f924755ff82e6bd59c3d7 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 21:08:59 +0200 Subject: [PATCH 56/71] Moved SqlServerAuditSpecification to separate PR --- CHANGELOG.md | 1 - appveyor.yml | 1 - azure-pipelines.yml | 1 - .../DSC_SqlServerAuditSpecification.psm1 | 1456 ----------------- ...DSC_SqlServerAuditSpecification.schema.mof | 53 - ...C_SqlServerAuditSpecification.strings.psd1 | 17 - ...-AddServerAuditSpecificationAdminAudit.ps1 | 65 - ...-AddServerAuditSpecificationLoginAudit.ps1 | 49 - .../3-AddServerAuditAuditChange.ps1 | 44 - .../4-AddMultipleServerAudits.ps1 | 90 - .../5-RemoveAuditSpecification.ps1 | 29 - ...erAuditSpecification.Integration.Tests.ps1 | 210 --- ...DSC_SqlServerAuditSpecification.config.ps1 | 227 --- 13 files changed, 2243 deletions(-) delete mode 100644 source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 delete mode 100644 source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof delete mode 100644 source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 delete mode 100644 source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 delete mode 100644 source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 delete mode 100644 source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 delete mode 100644 source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 delete mode 100644 source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 delete mode 100644 tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 delete mode 100644 tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 55862515a..69d0f7131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,7 +82,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for debugging of integration tests in AppVeyor. - Only run for pull requests - Add new resource SqlAudit. - - Add new resource SqlAuditSpecification. - CommonTestHelper - `Import-SqlModuleStub` - Added the optional parameter **PasThru** that, if used, will return the diff --git a/appveyor.yml b/appveyor.yml index 08dd5999f..fce8c5d7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -79,7 +79,6 @@ test_script: #'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' #'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' ## Group 4 #'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a83e97110..844d7d014 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -199,7 +199,6 @@ stages: 'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' 'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1' # Group 4 'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' 'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 deleted file mode 100644 index ee0ab55f0..000000000 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.psm1 +++ /dev/null @@ -1,1456 +0,0 @@ -$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 - Returns the current state of the server audit specification. - - .PARAMETER Name - Specifies the name of the server audit specification to be added or removed. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the server audit specification exist. - - .NOTES - DO NOT CHANGE THE CAPITALS IN THE PARAMETERS! These are used to find the - places to insert an underscore, see function Get-DatabaseObjectNameFromPSParamName - for more information. -#> -function Get-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is used which calls the command Connect-Sql')] - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName - ) - - Write-Verbose -Message ( - $script:localizedData.RetrievingAuditSpecificationInformation -f - $Name, - $ServerName, - $InstanceName - ) - - $returnValue = @{ - Ensure = 'Absent' - Name = $Name - ServerName = $ServerName - InstanceName = $InstanceName - AuditName = '' - ApplicationRoleChangePasswordGroup = $false - AuditChangeGroup = $false - BackupRestoreGroup = $false - BrokerLoginGroup = $false - DatabaseChangeGroup = $false - DatabaseLogoutGroup = $false - DatabaseMirroringLoginGroup = $false - DatabaseObjectAccessGroup = $false - DatabaseObjectChangeGroup = $false - DatabaseObjectOwnershipChangeGroup = $false - DatabaseObjectPermissionChangeGroup = $false - DatabaseOperationGroup = $false - DatabaseOwnershipChangeGroup = $false - DatabasePermissionChangeGroup = $false - DatabasePrincipalChangeGroup = $false - DatabasePrincipalImpersonationGroup = $false - DatabaseRoleMemberChangeGroup = $false - DbccGroup = $false - FailedDatabaseAuthenticationGroup = $false - FailedLoginGroup = $false - FulltextGroup = $false - LoginChangePasswordGroup = $false - LogoutGroup = $false - SchemaObjectAccessGroup = $false - SchemaObjectChangeGroup = $false - SchemaObjectOwnershipChangeGroup = $false - SchemaObjectPermissionChangeGroup = $false - ServerObjectChangeGroup = $false - ServerObjectOwnershipChangeGroup = $false - ServerObjectPermissionChangeGroup = $false - ServerOperationGroup = $false - ServerPermissionChangeGroup = $false - ServerPrincipalChangeGroup = $false - ServerPrincipalImpersonationGroup = $false - ServerRoleMemberChangeGroup = $false - ServerStateChangeGroup = $false - SuccessfulDatabaseAuthenticationGroup = $false - SuccessfulLoginGroup = $false - TraceChangeGroup = $false - UserChangePasswordGroup = $false - UserDefinedAuditGroup = $false - TransactionGroup = $false - Enabled = $false - } - - # Default parameters for the cmdlet Invoke-Query used throughout. - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - $dataSetAudit = Invoke-Query @invokeQueryParameters -WithResults -Query ( - 'Select - s.server_specification_id, - s.is_state_enabled, - a.name as auditName - FROM sys.server_audits AS a - JOIN sys.server_audit_specifications AS s - ON a.audit_guid = s.audit_guid - where s.name = ''{0}''' -f - $Name) - - if ($null -ne $dataSetAudit -and $dataSetAudit.Tables[0].Rows.Count -gt 0) - { - Write-Verbose -Message ( - $script:localizedData.AuditSpecificationExist -f $Name, $ServerName, $InstanceName - ) - - $dataSetRow = $dataSetAudit.Tables[0].Rows[0] - - $dataSetAuditSpecification = Invoke-Query @invokeQueryParameters -WithResults -Query ( - 'Select audit_action_name - from sys.server_audit_specification_details - where server_specification_id = {0}' -f - $dataSetRow.server_specification_id) - - # This should always happen! - if ($null -ne $dataSetAuditSpecification -and $dataSetAuditSpecification.Tables.Count -gt 0) - { - $resultSet = Convert-ToHashTable -DataTable $dataSetAuditSpecification.Tables[0] - - $returnValue['Ensure'] = 'Present' - $returnValue['AuditName'] = $dataSetRow.auditName - $returnValue['ApplicationRoleChangePasswordGroup'] = $resultSet['APPLICATION_ROLE_CHANGE_PASSWORD_GROUP'] - $returnValue['AuditChangeGroup'] = $resultSet['AUDIT_CHANGE_GROUP'] - $returnValue['BackupRestoreGroup'] = $resultSet['BACKUP_RESTORE_GROUP'] - $returnValue['BrokerLoginGroup'] = $resultSet['BROKER_LOGIN_GROUP'] - $returnValue['DatabaseChangeGroup'] = $resultSet['DATABASE_CHANGE_GROUP'] - $returnValue['DatabaseLogoutGroup'] = $resultSet['DATABASE_LOGOUT_GROUP'] - $returnValue['DatabaseMirroringLoginGroup'] = $resultSet['DATABASE_MIRRORING_LOGIN_GROUP'] - $returnValue['DatabaseObjectAccessGroup'] = $resultSet['DATABASE_OBJECT_ACCESS_GROUP'] - $returnValue['DatabaseObjectChangeGroup'] = $resultSet['DATABASE_OBJECT_CHANGE_GROUP'] - $returnValue['DatabaseObjectOwnershipChangeGroup'] = $resultSet['DATABASE_OBJECT_OWNERSHIP_CHANGE_GROUP'] - $returnValue['DatabaseObjectPermissionChangeGroup'] = $resultSet['DATABASE_OBJECT_PERMISSION_CHANGE_GROUP'] - $returnValue['DatabaseOperationGroup'] = $resultSet['DATABASE_OPERATION_GROUP'] - $returnValue['DatabaseOwnershipChangeGroup'] = $resultSet['DATABASE_OWNERSHIP_CHANGE_GROUP'] - $returnValue['DatabasePermissionChangeGroup'] = $resultSet['DATABASE_PERMISSION_CHANGE_GROUP'] - $returnValue['DatabasePrincipalChangeGroup'] = $resultSet['DATABASE_PRINCIPAL_CHANGE_GROUP'] - $returnValue['DatabasePrincipalImpersonationGroup'] = $resultSet['DATABASE_PRINCIPAL_IMPERSONATION_GROUP'] - $returnValue['DatabaseRoleMemberChangeGroup'] = $resultSet['DATABASE_ROLE_MEMBER_CHANGE_GROUP'] - $returnValue['DbccGroup'] = $resultSet['DBCC_GROUP'] - $returnValue['FailedDatabaseAuthenticationGroup'] = $resultSet['FAILED_DATABASE_AUTHENTICATION_GROUP'] - $returnValue['FailedLoginGroup'] = $resultSet['FAILED_LOGIN_GROUP'] - $returnValue['FulltextGroup'] = $resultSet['FULLTEXT_GROUP'] - $returnValue['LoginChangePasswordGroup'] = $resultSet['LOGIN_CHANGE_PASSWORD_GROUP'] - $returnValue['LogoutGroup'] = $resultSet['LOGOUT_GROUP'] - $returnValue['SchemaObjectAccessGroup'] = $resultSet['SCHEMA_OBJECT_ACCESS_GROUP'] - $returnValue['SchemaObjectChangeGroup'] = $resultSet['SCHEMA_OBJECT_CHANGE_GROUP'] - $returnValue['SchemaObjectOwnershipChangeGroup'] = $resultSet['SCHEMA_OBJECT_OWNERSHIP_CHANGE_GROUP'] - $returnValue['SchemaObjectPermissionChangeGroup'] = $resultSet['SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP'] - $returnValue['ServerObjectChangeGroup'] = $resultSet['SERVER_OBJECT_CHANGE_GROUP'] - $returnValue['ServerObjectOwnershipChangeGroup'] = $resultSet['SERVER_OBJECT_OWNERSHIP_CHANGE_GROUP'] - $returnValue['ServerObjectPermissionChangeGroup'] = $resultSet['SERVER_OBJECT_PERMISSION_CHANGE_GROUP'] - $returnValue['ServerOperationGroup'] = $resultSet['SERVER_OPERATION_GROUP'] - $returnValue['ServerPermissionChangeGroup'] = $resultSet['SERVER_PERMISSION_CHANGE_GROUP'] - $returnValue['ServerPrincipalChangeGroup'] = $resultSet['SERVER_PRINCIPAL_CHANGE_GROUP'] - $returnValue['ServerPrincipalImpersonationGroup'] = $resultSet['SERVER_PRINCIPAL_IMPERSONATION_GROUP'] - $returnValue['ServerRoleMemberChangeGroup'] = $resultSet['SERVER_ROLE_MEMBER_CHANGE_GROUP'] - $returnValue['ServerStateChangeGroup'] = $resultSet['SERVER_STATE_CHANGE_GROUP'] - $returnValue['SuccessfulDatabaseAuthenticationGroup'] = $resultSet['SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP'] - $returnValue['SuccessfulLoginGroup'] = $resultSet['SUCCESSFUL_LOGIN_GROUP'] - $returnValue['TraceChangeGroup'] = $resultSet['TRACE_CHANGE_GROUP'] - $returnValue['UserChangePasswordGroup'] = $resultSet['USER_CHANGE_PASSWORD_GROUP'] - $returnValue['UserDefinedAuditGroup'] = $resultSet['USER_DEFINED_AUDIT_GROUP'] - $returnValue['TransactionGroup'] = $resultSet['TRANSACTION_GROUP'] - $returnValue['Enabled'] = $dataSetRow.is_state_enabled - } - } - - return $returnValue -} - -<# - .SYNOPSIS - Creates, removes or updates a server audit specification to it's desired state. - - .PARAMETER Name - Specifies the name of the server audit specification to be added or removed. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the server audit specification exist. - - .PARAMETER Ensure - Specifies if the server audit specification should be present or absent. - If 'Present' then the server audit specification will be added to the instance - and, if needed, the server audit specification will be updated. If 'Absent' - then the server audit specification will be removed from the instance. - Defaults to 'Present'. - - .PARAMETER AuditName - Specifies the audit that should be used to store the server audit specification - events. - - .PARAMETER Enabled - Specifies if the server audit specification should be enabled or disabled. - - .PARAMETER ApplicationRoleChangePasswordGroup - Specifies if this audit specification should be on or off - - .PARAMETER AuditChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER BackupRestoreGroup - Specifies if this audit specification should be on or off - - .PARAMETER BrokerLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseLogoutGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseMirroringLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectAccessGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseOperationGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabasePermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabasePrincipalChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabasePrincipalImpersonationGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseRoleMemberChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DbccGroup - Specifies if this audit specification should be on or off - - .PARAMETER FailedDatabaseAuthenticationGroup - Specifies if this audit specification should be on or off - - .PARAMETER FailedLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER FulltextGroup - Specifies if this audit specification should be on or off - - .PARAMETER LoginChangePasswordGroup - Specifies if this audit specification should be on or off - - .PARAMETER LogoutGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectAccessGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerObjectChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerObjectOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerObjectPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerOperationGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerPrincipalChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerPrincipalImpersonationGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerRoleMemberChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerStateChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER SuccessfulDatabaseAuthenticationGroup - Specifies if this audit specification should be on or off - - .PARAMETER SuccessfulLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER TraceChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER UserChangePasswordGroup - Specifies if this audit specification should be on or off - - .PARAMETER UserDefinedAuditGroup - Specifies if this audit specification should be on or off - - .PARAMETER TransactionGroup - Specifies if this audit specification should be on or off -#> -function Set-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is used which calls the command Connect-Sql')] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter()] - [System.String] - $AuditName, - - [Parameter()] - [System.Boolean] - $ApplicationRoleChangePasswordGroup = $false, - - [Parameter()] - [System.Boolean] - $AuditChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $BackupRestoreGroup = $false, - - [Parameter()] - [System.Boolean] - $BrokerLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseLogoutGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseMirroringLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectAccessGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseOperationGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabasePermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabasePrincipalChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabasePrincipalImpersonationGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseRoleMemberChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DbccGroup = $false, - - [Parameter()] - [System.Boolean] - $FailedDatabaseAuthenticationGroup = $false, - - [Parameter()] - [System.Boolean] - $FailedLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $FulltextGroup = $false, - - [Parameter()] - [System.Boolean] - $LoginChangePasswordGroup = $false, - - [Parameter()] - [System.Boolean] - $LogoutGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectAccessGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerObjectChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerObjectOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerObjectPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerOperationGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerPrincipalChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerPrincipalImpersonationGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerRoleMemberChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerStateChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $SuccessfulDatabaseAuthenticationGroup = $false, - - [Parameter()] - [System.Boolean] - $SuccessfulLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $TraceChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $UserChangePasswordGroup = $false, - - [Parameter()] - [System.Boolean] - $UserDefinedAuditGroup = $false, - - [Parameter()] - [System.Boolean] - $TransactionGroup = $false, - - [Parameter()] - [System.Boolean] - $Enabled = $false, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter()] - [System.Boolean] - $Force = $false - ) - - Write-Verbose -Message ( - $script:localizedData.SetAuditSpecification -f $Name, $ServerName, $InstanceName - ) - - $TargetResourceParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Name = $Name - } - - # Get-TargetResource will also help us to test if the database exist. - $getTargetResourceResult = Get-TargetResource @TargetResourceParameters - - # Default parameters for the cmdlet Invoke-Query used throughout. - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - $desiredValues = @{ } + $PSBoundParameters - - $auditSpecificationAddDropString = Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues - - Write-Debug -Message $( - Get-AuditSpecificationMutationString -CurrentValues $getTargetResourceResult -DesiredValues $desiredValues - ) - - $recreateAudit = $false - - if ($getTargetResourceResult.Ensure -eq $Ensure) - { - if ($Ensure -eq 'Present') - { - # Update, if needed. - Write-Verbose -Message ( - $script:localizedData.CreateAuditSpecification -f $Name, $serverName, $instanceName - ) - - Disable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName - - try - { - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT SPECIFICATION [{0}] FOR SERVER AUDIT [{1}] {2}' -f - $Name, - $AuditName, - $auditSpecificationAddDropString - ) - } - catch - { - #If something went wrong, try to recreate te resource. - $recreateAudit = $true - - $errorMessage = $script:localizedData.FailedUpdateAuditSpecification -f $Name, $serverName, $instanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - - if ($Enabled -eq $true) - { - Enable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName - } - } - } - - # Throw if not opt-in to re-create server audit specification. - if ($recreateAudit -and -not $Force) - { - $errorMessage = $script:localizedData.ForceNotEnabled - New-InvalidOperationException -Message $errorMessage - } - - if (($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) - { - Write-Verbose -Message ( - $script:localizedData.DropAuditSpecification -f $Name, $serverName, $instanceName - ) - - Disable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName - - # Drop the server audit. - try - { - Invoke-Query @invokeQueryParameters -Query ( - 'DROP SERVER AUDIT SPECIFICATION [{0}];' -f $Name - ) - } - catch - { - $errorMessage = $script:localizedData.FailedDropAuditSpecification -f $Name, $serverName, $instanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - - <# - This evaluation is made to handle creation and re-creation of a database - user to minimize the logic when the user has a different user type, or - when there are restrictions on altering an existing database user. - #> - if (($Ensure -eq 'Present' -and $getTargetResourceResult.Ensure -ne $Ensure) -or $recreateAudit) - { - Write-Verbose -Message ( - $script:localizedData.CreateAuditSpecification -f $Name, $serverName, $instanceName - ) - - try - { - #when target audit already has an audit specification, graceful abort. - #only one audit spec for each audit per database/server can exist. - $DataSetAudit = Invoke-Query @invokeQueryParameters -WithResults -Query ( - 'select sas.name from - sys.server_audits sa inner join - sys.server_audit_specifications sas - on sa.audit_guid = sas.audit_guid - where sa.name = ''{0}''' -f - $AuditName) - if ($null -eq $DataSetAudit -or $DataSetAudit.Tables[0].Rows.Count -gt 0) - { - $errorMessage = $script:localizedData.AuditAlreadyInUse -f - $AuditName, - $Name, - $serverName, - $instanceName, - $DataSetAudit.Tables[0].Rows[0] - New-InvalidOperationException -Message $errorMessage #-ErrorRecord $_ - } - } - catch - { - $errorMessage = $script:localizedData.FailedCreateAuditSpecification -f $Name, $serverName, $instanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - - # Create, if needed and possible. - try - { - Invoke-Query @invokeQueryParameters -Query ( - 'CREATE SERVER AUDIT SPECIFICATION [{0}] FOR SERVER AUDIT [{1}] {2}' -f - $Name, - $AuditName, - $auditSpecificationAddDropString) - - if ($Enabled -eq $true) - { - Enable-AuditSpecification -ServerName $serverName -Name $Name -InstanceName $instanceName - } - } - catch - { - $errorMessage = $script:localizedData.FailedCreateAuditSpecification -f $Name, $serverName, $instanceName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } -} - - - -<# - .SYNOPSIS - Determines if the server audit specification is in desired state. - - .PARAMETER Name - Specifies the name of the server audit specification to be added or removed. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the server audit specification exist. - - .PARAMETER Ensure - Specifies if the server audit specification should be present or absent. - If 'Present' then the server audit specification will be added to the instance - and, if needed, the server audit specification will be updated. If 'Absent' - then the server audit specification will be removed from the instance. - Defaults to 'Present'. - - .PARAMETER AuditName - Specifies the audit that should be used to store the server audit specification - events. - - .PARAMETER Enabled - Specifies if the server audit specification should be enabled or disabled. - - .PARAMETER ApplicationRoleChangePasswordGroup - Specifies if this audit specification should be on or off - - .PARAMETER AuditChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER BackupRestoreGroup - Specifies if this audit specification should be on or off - - .PARAMETER BrokerLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseLogoutGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseMirroringLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectAccessGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseObjectPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseOperationGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabasePermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabasePrincipalChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabasePrincipalImpersonationGroup - Specifies if this audit specification should be on or off - - .PARAMETER DatabaseRoleMemberChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER DbccGroup - Specifies if this audit specification should be on or off - - .PARAMETER FailedDatabaseAuthenticationGroup - Specifies if this audit specification should be on or off - - .PARAMETER FailedLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER FulltextGroup - Specifies if this audit specification should be on or off - - .PARAMETER LoginChangePasswordGroup - Specifies if this audit specification should be on or off - - .PARAMETER LogoutGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectAccessGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER SchemaObjectPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerObjectChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerObjectOwnershipChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerObjectPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerOperationGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerPermissionChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerPrincipalChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerPrincipalImpersonationGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerRoleMemberChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER ServerStateChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER SuccessfulDatabaseAuthenticationGroup - Specifies if this audit specification should be on or off - - .PARAMETER SuccessfulLoginGroup - Specifies if this audit specification should be on or off - - .PARAMETER TraceChangeGroup - Specifies if this audit specification should be on or off - - .PARAMETER UserChangePasswordGroup - Specifies if this audit specification should be on or off - - .PARAMETER UserDefinedAuditGroup - Specifies if this audit specification should be on or off - - .PARAMETER TransactionGroup - Specifies if this audit specification should be on or off -#> -function Test-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Invoke-Query is called when Get-TargetResource is called')] - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter()] - [System.String] - $AuditName, - - [Parameter()] - [System.Boolean] - $ApplicationRoleChangePasswordGroup = $false, - - [Parameter()] - [System.Boolean] - $AuditChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $BackupRestoreGroup = $false, - - [Parameter()] - [System.Boolean] - $BrokerLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseLogoutGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseMirroringLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectAccessGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseObjectPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseOperationGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabasePermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabasePrincipalChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabasePrincipalImpersonationGroup = $false, - - [Parameter()] - [System.Boolean] - $DatabaseRoleMemberChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $DbccGroup = $false, - - [Parameter()] - [System.Boolean] - $FailedDatabaseAuthenticationGroup = $false, - - [Parameter()] - [System.Boolean] - $FailedLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $FulltextGroup = $false, - - [Parameter()] - [System.Boolean] - $LoginChangePasswordGroup = $false, - - [Parameter()] - [System.Boolean] - $LogoutGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectAccessGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $SchemaObjectPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerObjectChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerObjectOwnershipChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerObjectPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerOperationGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerPermissionChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerPrincipalChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerPrincipalImpersonationGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerRoleMemberChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $ServerStateChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $SuccessfulDatabaseAuthenticationGroup = $false, - - [Parameter()] - [System.Boolean] - $SuccessfulLoginGroup = $false, - - [Parameter()] - [System.Boolean] - $TraceChangeGroup = $false, - - [Parameter()] - [System.Boolean] - $UserChangePasswordGroup = $false, - - [Parameter()] - [System.Boolean] - $UserDefinedAuditGroup = $false, - - [Parameter()] - [System.Boolean] - $TransactionGroup = $false, - - [Parameter()] - [System.Boolean] - $Enabled = $false, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter()] - [System.Boolean] - $Force = $false - ) - - Write-Verbose -Message ( - $script:localizedData.EvaluateAuditSpecification -f $Name, $ServerName, $InstanceName - ) - - $TargetResourceParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Name = $Name - } - - # Get-TargetResource will also help us to test if the audit exist. - $getTargetResourceResult = Get-TargetResource @TargetResourceParameters - - if ($getTargetResourceResult.Ensure -eq $Ensure) - { - if ($Ensure -eq 'Present') - { - <# - Make sure default values are part of desired values if the user did - not specify them in the configuration. - #> - $desiredValues = @{ } + $PSBoundParameters - $desiredValues['Ensure'] = $Ensure - - $testDscParameterStateParameters = @{ - CurrentValues = $getTargetResourceResult - DesiredValues = $desiredValues - Properties = @( - 'Ensure' - 'AuditName' - 'ApplicationRoleChangePasswordGroup' - 'AuditChangeGroup' - 'BackupRestoreGroup' - 'BrokerLoginGroup' - 'DatabaseChangeGroup' - 'DatabaseLogoutGroup' - 'DatabaseMirroringLoginGroup' - 'DatabaseObjectAccessGroup' - 'DatabaseObjectChangeGroup' - 'DatabaseObjectOwnershipChangeGroup' - 'DatabaseObjectPermissionChangeGroup' - 'DatabaseOperationGroup' - 'DatabaseOwnershipChangeGroup' - 'DatabasePermissionChangeGroup' - 'DatabasePrincipalChangeGroup' - 'DatabasePrincipalImpersonationGroup' - 'DatabaseRoleMemberChangeGroup' - 'DbccGroup' - 'FailedDatabaseAuthenticationGroup' - 'FailedLoginGroup' - 'FulltextGroup' - 'LoginChangePasswordGroup' - 'LogoutGroup' - 'SchemaObjectAccessGroup' - 'SchemaObjectChangeGroup' - 'SchemaObjectOwnershipChangeGroup' - 'SchemaObjectPermissionChangeGroup' - 'ServerObjectChangeGroup' - 'ServerObjectOwnershipChangeGroup' - 'ServerObjectPermissionChangeGroup' - 'ServerOperationGroup' - 'ServerPermissionChangeGroup' - 'ServerPrincipalChangeGroup' - 'ServerPrincipalImpersonationGroup' - 'ServerRoleMemberChangeGroup' - 'ServerStateChangeGroup' - 'SuccessfulDatabaseAuthenticationGroup' - 'SuccessfulLoginGroup' - 'TraceChangeGroup' - 'UserChangePasswordGroup' - 'UserDefinedAuditGroup' - 'TransactionGroup' - 'Enabled' - ) - TurnOffTypeChecking = $true - # TODO: Remove verbose - Verbose = $true - } - - $propertiesNotInDesiredState = Compare-DscParameterState @testDscParameterStateParameters - - if ($propertiesNotInDesiredState) - { - $testTargetResourceReturnValue = $false - } - else - { - $testTargetResourceReturnValue = $true - } - } - else - { - $testTargetResourceReturnValue = $true - } - } - else - { - $testTargetResourceReturnValue = $false - } - - if ($testTargetResourceReturnValue) - { - Write-Verbose -Message $script:localizedData.InDesiredState - } - else - { - Write-Verbose -Message $script:localizedData.NotInDesiredState - } - - return $testTargetResourceReturnValue -} - -<# - .SYNOPSIS - Converts a data table to a HashTable - - .PARAMETER DataTable - The data table to be converted to a hashtable. The data table can have one - or two columns. When the data table has one column, the hashtable will use - $true as value for the second column. -#> -function Convert-ToHashTable -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [system.Data.DataTable] - $DataTable - ) - - $resultSet = @{} - - foreach ($item in $DataTable) - { - if ($DataTable.Columns.Count -eq 1) - { - $resultSet.Add($item[0], $true) - } - if ($DataSet.Columns.Count -eq 2) - { - $resultSet.Add($item[0], $item[1]) - } - } - return $resultSet -} - -<# - .SYNOPSIS - Disables a server audit specification. - - .PARAMETER Name - Specifies the name of the server audit specification to be disabled. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the audit exist. -#> -function Disable-AuditSpecification -{ - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name - ) - - Write-Verbose -Message ( - $script:localizedData.DisableAuditSpecification -f $Name, $serverName, $instanceName - ) - - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT SPECIFICATION [{0}] WITH (STATE = OFF);' -f $Name - ) -} - -<# - .SYNOPSIS - Enables a server audit specification. - - .PARAMETER Name - Specifies the name of the server audit specification to be enabled. - - .PARAMETER ServerName - Specifies the host name of the SQL Server on which the instance exist. - - .PARAMETER InstanceName - Specifies the SQL instance in which the audit exist. -#> -function Enable-AuditSpecification -{ - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $ServerName, - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name - ) - - Write-Verbose -Message ( - $script:localizedData.EnableAuditSpecification -f $Name, $serverName, $instanceName - ) - - $invokeQueryParameters = @{ - ServerName = $ServerName - InstanceName = $InstanceName - Database = 'MASTER' - } - - Invoke-Query @invokeQueryParameters -Query ( - 'ALTER SERVER AUDIT SPECIFICATION [{0}] WITH (STATE = ON);' -f $Name - ) -} - -<# - .SYNOPSIS - Converts the parameter name to the equivalent database object name. - - .PARAMETER InString - Specifies the name of the parameter to be converted to a server policy string. - - .EXAMPLE - $strKey = 'AuditChangeGroup' - $ret = Get-DatabaseObjectNameFromPSParamName -InString $strKey - $ret - Should return 'AUDIT_CHANGE_GROUP' in $ret - - .NOTES - Trough this function the capital letters in Get- Test- and Set-TargetResource - parameters get converted, e.g. FulltextGroup and AuditChangeGroup to - FULLTEXT_GROUP and AUDIT_CHANGE_GROUP. - This is the naming of audit specification within SQL Server. - - DO NOT CHANGE THE CAPITALS IN THE PARAMETERS! - These are used to find the places to insert an underscore. -#> -function Get-DatabaseObjectNameFromPSParamName -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $InString - ) - - return ($InString -creplace '([A-Z\W_]|\d+)(? -function Get-AuditSpecificationMutationString -{ - [CmdletBinding()] - [OutputType([System.String])] - param - ( - [Parameter(Mandatory = $true)] - [Hashtable] - $CurrentValues, - - [Parameter(Mandatory = $true)] - [hashtable] - $DesiredValues - ) - - $resultString = '' - - $CurrentValues.GetEnumerator() | ForEach-Object { - if ($null -eq $_.Value -or $_.Value -eq '') - { - $val = 'False' - } - else - { - $val = $_.Value - } - $resultString += Test-SingleRow -CurrentKey $_.Key -CurrentValue $val -DesiredValues $DesiredValues - } - - return $resultString.TrimEnd(',') -} - -<# - .SYNOPSIS - Builds a ADD/DROP string for all the needed changes of the database audit - specification - - .PARAMETER $CurrentKey - Specifies the current Key to be checked against the hash DesiredValues. - - .PARAMETER $CurrentValue - Specifies the current Value to be checked against the hash DesiredValues. - - .PARAMETER $DesiredValues - Specifies the hashtable containing the desired settings. Usually this - should be all of the input parameters of Set-TargetResource. -#> -function Test-SingleRow -{ - [CmdletBinding()] - [OutputType([System.String])] - param - ( - [Parameter(Mandatory = $true)] - [String] - $CurrentKey, - - [Parameter(Mandatory = $true)] - [String] - $CurrentValue, - - [Parameter(Mandatory = $true)] - [hashtable] - $DesiredValues - ) - - $return = '' - - if ($CurrentKey -ne 'Name' -and - $CurrentKey -ne 'ServerName' -and - $CurrentKey -ne 'InstanceName' -and - $CurrentKey -ne 'AuditName' -and - $CurrentKey -ne 'Enabled' -and - $CurrentKey -ne 'Ensure' -and - $CurrentKey -ne 'Force') - { - if ($null -eq $DesiredValues.$CurrentKey) - { - $desiredValue = 'False' - } - else - { - $desiredValue = $DesiredValues.$CurrentKey - } - - #When not equal - if ($CurrentValue -ne $desiredValue) - { - $DatabaseCompatibleKeyString = Get-DatabaseObjectNameFromPSParamName -InString $CurrentKey - - if ($desiredValue -eq 'True') - { - #When desired, add it. - $return = 'ADD ({0}),' -f $DatabaseCompatibleKeyString - } - else - { - #When not wanted, drop it. - $return = 'DROP ({0}),' -f $DatabaseCompatibleKeyString - } - } - } - - return $return -} diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof b/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof deleted file mode 100644 index 8aff2e0f1..000000000 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/DSC_SqlServerAuditSpecification.schema.mof +++ /dev/null @@ -1,53 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("SqlServerAuditSpecification")] -class DSC_SqlServerAuditSpecification : OMI_BaseResource -{ - [Key, Description("Specifies the host name of the SQL Server on which the instance exist.")] String ServerName; - [Key, Description("Specifies the SQL instance in which the Audit exist.")] String InstanceName; - [Key, Description("Specifies the name of the SQL audit specification to be added or removed.")] String Name; - [Write, Description("Specifies if the audit specification should be enabled. Defaults to $false")] Boolean Enabled; - [Write, Description("Specifies the audit to be used as storage.")] String AuditName; - [Write, Description("Specifies if this property should be audited.")] Boolean ApplicationRoleChangePasswordGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean AuditChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean BackupRestoreGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean BrokerLoginGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseLogoutGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseMirroringLoginGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectAccessGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectOwnershipChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseObjectPermissionChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseOperationGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseOwnershipChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabasePermissionChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabasePrincipalChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabasePrincipalImpersonationGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DatabaseRoleMemberChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean DbccGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean FailedDatabaseAuthenticationGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean FailedLoginGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean FulltextGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean LoginChangePasswordGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean LogoutGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectAccessGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectOwnershipChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean SchemaObjectPermissionChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerObjectChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerObjectOwnershipChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerObjectPermissionChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerOperationGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerPermissionChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerPrincipalChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerPrincipalImpersonationGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerRoleMemberChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean ServerStateChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean SuccessfulDatabaseAuthenticationGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean SuccessfulLoginGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean TraceChangeGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean UserChangePasswordGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean UserDefinedAuditGroup; - [Write, Description("Specifies if this property should be audited.")] Boolean TransactionGroup; - [Write, Description("Specifies if the audit should be present or absent. If 'Present' then the audit will be added to the server and, if needed, the audit will be updated. If 'Absent' then the audit will be removed from the server. Defaults to 'Present'."), ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure; - [Write, Description("Specifies if it is allowed to re-create the server audit when the DestinationType changes. Defaults to $false not allowing server audits to be re-created.")] Boolean Force; -}; diff --git a/source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 b/source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 deleted file mode 100644 index d3e9c9b3b..000000000 --- a/source/DSCResources/DSC_SqlServerAuditSpecification/en-US/DSC_SqlServerAuditSpecification.strings.psd1 +++ /dev/null @@ -1,17 +0,0 @@ -ConvertFrom-StringData @' - RetrievingAuditSpecificationInformation = Retrieving information about Audit specification '{0}' from the server '{1}' instance '{2}'. (SSAS0001) - EvaluateAuditSpecification = Determining if the audit specification '{0}' on server '{1}' instance '{2}' is in the desired state. (SSAS0002) - AuditSpecificationExist = The audit specification '{0}' exist in on server '{1}' instance '{2}'. (SSAS0003) - InDesiredState = The audit specification is in desired state. (SSAS0004) - NotInDesiredState = The audit specification is not in desired state. (SSAS0005) - DisableAuditSpecification = Disabling audit audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0006) - EnableAuditSpecification = Enabling audit audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0007) - CreateAuditSpecification = Creating the audit specification '{0}' on server '{1}' instance '{2}'. (SSAS008) - FailedCreateAuditSpecification = Failed creating audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0009) - AuditAlreadyInUse = Audit {0} for audit specification '{1}' on server '{2}' instance '{3}' is already in use for audit specification '{4}' (SSAS0010) - DropAuditSpecification = Removing the audit specification '{0}' from server '{1}' instance '{2}'. (SSAS0011) - FailedDropAuditSpecification = Failed removing the audit specification '{0}' from server '{1}' instance '{2}'. (SSAS0012) - SetAuditSpecification = Setting the audit specification '{0}' on server '{1}' instance '{2}' to the desired state. (SSAS0013) - FailedUpdateAuditSpecification = Failed updating audit specification '{0}' on server '{1}' instance '{2}'. (SSAS0014) - ForceNotEnabled = Unable to re-create the server audit. The server audit needs to be re-created but the configuration has not opt-in to re-create the audit. To opt-in set the parameter Force to $true. (SSAS0015) -'@ diff --git a/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 deleted file mode 100644 index 0288f2607..000000000 --- a/source/Examples/Resources/SqlServerAuditSpecification/1-AddServerAuditSpecificationAdminAudit.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -<# - .EXAMPLE - This example shows how to ensure that an audit destination - is absent on the instance sqltest.company.local\DSC. -#> -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName SqlServerDsc - - node localhost - { - SqlAudit SecurityLogAudit_Server - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit' - LogType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlServerAuditSpecification 'ServerAuditSpecification_AdminAudit' - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AdminAudit' - AuditName = 'SecLogAudit' - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlAudit]SecurityLogAudit_Server' - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 deleted file mode 100644 index bf8e5a29b..000000000 --- a/source/Examples/Resources/SqlServerAuditSpecification/2-AddServerAuditSpecificationLoginAudit.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -<# - .EXAMPLE - This example shows how to ensure that an audit destination - is absent on the instance sqltest.company.local\DSC. -#> -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName SqlServerDsc - - node localhost - { - SqlAudit SecurityLogAudit_Server - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit' - LogType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlServerAuditSpecification 'ServerAuditSpecification_AdminAudit' - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AdminAudit' - AuditName = 'SecLogAudit' - Enabled = $true - DatabaseLogoutGroup = $true - FailedDatabaseAuthenticationGroup = $true - FailedLoginGroup = $true - LoginChangePasswordGroup = $true - LogoutGroup = $true - SuccessfulDatabaseAuthenticationGroup = $true - SuccessfulLoginGroup = $true - DependsOn = '[SqlAudit]SecurityLogAudit_Server' - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 deleted file mode 100644 index f4fa11bac..000000000 --- a/source/Examples/Resources/SqlServerAuditSpecification/3-AddServerAuditAuditChange.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -<# - .EXAMPLE - This example shows how to ensure that an audit destination - is absent on the instance sqltest.company.local\DSC. -#> -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName SqlServerDsc - - node localhost - { - SqlAudit SecurityLogAudit_Server - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit' - LogType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlServerAuditSpecification 'ServerAuditSpecification_AuditAudit' - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AuditAudit' - AuditName = 'SecLogAudit' - Enabled = $true - AuditChangeGroup = $true - TraceChangeGroup = $true - DependsOn = "[SqlAudit]SecurityLogAudit_Server" - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 deleted file mode 100644 index e73789ca0..000000000 --- a/source/Examples/Resources/SqlServerAuditSpecification/4-AddMultipleServerAudits.ps1 +++ /dev/null @@ -1,90 +0,0 @@ -<# - .EXAMPLE - This example shows how to add multiple audit specifications to the same instance. - Each audit can only contain one audit specification. -#> -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName SqlServerDsc - - node localhost - { - SqlAudit SecurityLogAudit_Server01 - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit01' - LogType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlAudit SecurityLogAudit_Server02 - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'SecLogAudit02' - LogType = 'SecurityLog' - Enabled = $true - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlServerAuditSpecification ServerAuditSpecification_AuditAudit - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AuditAudit' - AuditName = 'SecLogAudit01' - Enabled = $true - AuditChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlAudit]SecurityLogAudit_Server01' - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlServerAuditSpecification ServerAuditSpecification_AdminAudit - { - Ensure = 'Present' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AdminAudit' - AuditName = 'SecLogAudit02' - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlAudit]SecurityLogAudit_Server02' - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 b/source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 deleted file mode 100644 index 3a4497229..000000000 --- a/source/Examples/Resources/SqlServerAuditSpecification/5-RemoveAuditSpecification.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -<# - .EXAMPLE - This example shows how to ensure that an audit destination - is absent on the instance sqltest.company.local\DSC. -#> -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName SqlServerDsc - - node localhost - { - SqlServerAuditSpecification 'ServerAuditSpecification_AdminAudit' - { - Ensure = 'Absent' - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - Name = 'AdminAudit' - AuditName = 'SecLogAudit' - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 deleted file mode 100644 index dcd0c8dd3..000000000 --- a/tests/Integration/DSC_SqlServerAuditSpecification.Integration.Tests.ps1 +++ /dev/null @@ -1,210 +0,0 @@ -BeforeDiscovery { - 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.' - } - - <# - Need to define that variables here to be used in the Pester Discover to - build the ForEach-blocks. - #> - $script:dscResourceFriendlyName = 'SqlServerAuditSpecification' - $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" -} - -BeforeAll { - # Need to define the variables here which will be used in Pester Run. - $script:dscModuleName = 'SqlServerDsc' - $script:dscResourceFriendlyName = 'SqlServerAuditSpecification' - $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" - - $script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Integration' - - $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" - . $configFile -} - -Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019') { - BeforeAll { - $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" - } - - Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_AddAudit1_Config" - ) { - BeforeAll { - $configurationName = $_ - } - - AfterAll { - Wait-ForIdleLcm - } - - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName1 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_AddSecLogAudit_Config" - ) { - BeforeAll { - $configurationName = $_ - } - - AfterAll { - Wait-ForIdleLcm - } - - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Present' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName2 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } - - Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_RemoveAudit1_Config" - ) { - BeforeAll { - $configurationName = $_ - } - - AfterAll { - Wait-ForIdleLcm - } - - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.Ensure | Should -Be 'Absent' - $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditSpecificationName1 - $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName - $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - } -} diff --git a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 b/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 deleted file mode 100644 index 8efe87907..000000000 --- a/tests/Integration/DSC_SqlServerAuditSpecification.config.ps1 +++ /dev/null @@ -1,227 +0,0 @@ -#region HEADER -# Integration Test Config Template Version: 1.2.0 -#endregion - -$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') -if (Test-Path -Path $configFile) -{ - <# - Allows reading the configuration data from a JSON file, - for real testing scenarios outside of the CI. - #> - $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json -} -else -{ - $ConfigurationData = @{ - AllNodes = @( - @{ - NodeName = 'localhost' - CertificateFile = $env:DscPublicCertificatePath - - UserName = "$env:COMPUTERNAME\SqlAdmin" - Password = 'P@ssw0rd1' - - ServerName = $env:COMPUTERNAME - InstanceName = 'DSCSQLTEST' - - AuditName1 = 'FileAudit' - Path1 = 'C:\Temp\audit\' - MaximumFileSize1 = 10 - MaximumFileSizeUnit1 = 'Megabyte' - MaximumRolloverFiles1 = 11 - - AuditName2 = 'SecLogAudit' - LogType2 = 'SecurityLog' - - AuditSpecificationName1 = 'AdminAudit1' - AuditSpecificationName2 = 'AdminAudit2' - } - ) - } -} - -<# - .SYNOPSIS - Creates a Server Audit with File destination. -#> -Configuration DSC_SqlServerAuditSpecification_AddAudit1_Config -{ - Import-DscResource -ModuleName 'SqlServerDsc' - - node $AllNodes.NodeName - { - SqlAudit 'Integration_TestPrepare' - { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditName1 - Path = $Node.Path1 - MaximumFileSize = $Node.MaximumFileSize1 - MaximumFileSizeUnit = $Node.MaximumFileSizeUnit1 - MaximumRolloverFiles = $Node.MaximumRolloverFiles1 - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) - } - - SqlServerAuditSpecification 'Integration_Test' - { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName1 - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlAudit]Integration_TestPrepare' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} - -<# - .SYNOPSIS - Creates a audit to the security log, with a filer. -#> -Configuration DSC_SqlServerAuditSpecification_AddSecLogAudit_Config -{ - Import-DscResource -ModuleName 'SqlServerDsc' - - node $AllNodes.NodeName - { - SqlAudit 'Integration_TestPrepare' - { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditName2 - LogType = $Node.LogType2 - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) - } - - SqlServerAuditSpecification 'Integration_Test' - { - Ensure = 'Present' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName2 - AuditName = $Node.AuditName2 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlAudit]Integration_TestPrepare' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} - -<# - .SYNOPSIS - Removes the file audit. -#> -Configuration DSC_SqlServerAuditSpecification_RemoveAudit1_Config -{ - Import-DscResource -ModuleName 'SqlServerDsc' - - node $AllNodes.NodeName - { - SqlAudit 'Integration_TestPrepare' - { - Ensure = 'Absent' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditName1 - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) - } - - SqlServerAuditSpecification 'Integration_Test' - { - Ensure = 'Absent' - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - Name = $Node.AuditSpecificationName1 - AuditName = $Node.AuditName1 - Enabled = $true - AuditChangeGroup = $true - BackupRestoreGroup = $true - DatabaseObjectChangeGroup = $true - DatabaseObjectOwnershipChangeGroup = $true - DatabaseObjectPermissionChangeGroup = $true - DatabaseOwnershipChangeGroup = $true - DatabasePermissionChangeGroup = $true - DatabasePrincipalChangeGroup = $true - DatabasePrincipalImpersonationGroup = $true - DatabaseRoleMemberChangeGroup = $true - SchemaObjectChangeGroup = $true - SchemaObjectOwnershipChangeGroup = $true - SchemaObjectPermissionChangeGroup = $true - ServerObjectChangeGroup = $true - ServerObjectOwnershipChangeGroup = $true - ServerObjectPermissionChangeGroup = $true - ServerOperationGroup = $true - ServerPermissionChangeGroup = $true - ServerPrincipalChangeGroup = $true - ServerPrincipalImpersonationGroup = $true - ServerRoleMemberChangeGroup = $true - ServerStateChangeGroup = $true - TraceChangeGroup = $true - DependsOn = '[SqlAudit]Integration_TestPrepare' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} From 987c211fa68825aabab63565c1a05e2592861d53 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 9 Aug 2022 21:29:18 +0200 Subject: [PATCH 57/71] Fix link --- source/Examples/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/source/Examples/README.md b/source/Examples/README.md index 98c764eb1..60629599c 100644 --- a/source/Examples/README.md +++ b/source/Examples/README.md @@ -16,7 +16,6 @@ These are the links to the examples for each individual resource. - [SqlAlias](Resources/SqlAlias) - [SqlAlwaysOnService](Resources/SqlAlwaysOnService) - [SqlAudit](Resources/SqlAudit) -- [SqlServerAuditSpecification](Resources/SqlServerAuditSpecification) - [SqlDatabase](Resources/SqlDatabase) - [SqlDatabaseDefaultLocation](Resources/SqlDatabaseDefaultLocation) - [SqlDatabasePermission](Resources/SqlDatabasePermission) From fa0c6cee44913fc6d32a73ed040b688492cfc446 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 08:45:45 +0200 Subject: [PATCH 58/71] Fix integration tests --- .../DSC_SqlAudit.Integration.Tests.ps1 | 59 +++++++++++++++++++ tests/Integration/DSC_SqlAudit.config.ps1 | 25 ++++++++ tests/Integration/README.md | 10 ++++ 3 files changed, 94 insertions(+) diff --git a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 index d21bcb047..3ce8652af 100644 --- a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 @@ -280,4 +280,63 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', Test-DscConfiguration -Verbose | Should -Be 'True' } } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveSecLogAudit_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.AuditName2 + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.LogType | Should -BeNullOrEmpty + $resourceCurrentState.AuditFilter | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } } diff --git a/tests/Integration/DSC_SqlAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 index 293b42cdb..06928a0ae 100644 --- a/tests/Integration/DSC_SqlAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -151,3 +151,28 @@ Configuration DSC_SqlAudit_RemoveAudit1_Config } } } + + +<# + .SYNOPSIS + Removes the log audit. +#> +Configuration DSC_SqlAudit_RemoveSecLogAudit_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlAudit 'Integration_Test' + { + Ensure = 'Absent' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + Name = $Node.AuditName2 + + Credential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} diff --git a/tests/Integration/README.md b/tests/Integration/README.md index 7ec990759..964286755 100644 --- a/tests/Integration/README.md +++ b/tests/Integration/README.md @@ -444,6 +444,16 @@ and the named instance `DSCSQLTEST` have the feature `REPLICATION` installed. The integration test will not leave anything on any instance. +### SqlAudit + +**Run order:** 3 + +**Depends on:** SqlSetup + +This integration tests depends on the named instance `DSCSQLTEST`. + +The integration test will not leave anything on any instance. + ### SqlScript **Run order:** 4 From 49e09344ad79277876fba7970ed4dbf0264d5ba0 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 10:07:15 +0200 Subject: [PATCH 59/71] Fix appveyor.yml and CHANGELOG --- CHANGELOG.md | 5 +++++ appveyor.yml | 27 +++++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d0f7131..02f3e3f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 in desired state. - `ResourceBase` - class that can be inherited by class-based resource and provides functionality meant simplify the creating of class-based resource. + - `SqlResourceBase` - class that can be inherited by class-based resource and + provides default DSC properties and method for get a `[Server]`-object. - `ServerPermission` - complex type for the DSC resource SqlPermission. - The following private functions were added to the module (see comment-based help for more information): @@ -313,6 +315,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Minor code cleanup. - `ConvertTo-Reason` - Fix to handle `$null` values on Windows PowerShell. + - If the property name contain the word 'Path' the value will be parsed to + replace backslash or slashes at the end of the string, e.g. `'/mypath/'` + will become `'/mypath'`. - `ResourceBase` - Now handles `Ensure` correctly from derived `GetCurrentState()`. But requires that the `GetCurrentState()` only return key property if object diff --git a/appveyor.yml b/appveyor.yml index fce8c5d7f..cc95c4844 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,31 +19,31 @@ environment: matrix: # DEBUG: Comment and un-comment the different SQL Server version that should be tested. - TEST_CONFIGURATION: Integration_SQL2016 - #- TEST_CONFIGURATION: Integration_SQL2017 + - TEST_CONFIGURATION: Integration_SQL2017 #- TEST_CONFIGURATION: Integration_SQL2019 # DEBUG: See section on_finish last in this file on how to block build to keep RDP open. -# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request +# DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request init: - ps: | # Only run for pull requests - #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } else { 'Not a pull request, skipping.'} iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) -# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request +# DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request install: - ps: | # Only run for pull requests - #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } winrm quickconfig -quiet -# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request +# DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request build_script: - pwsh: | # Only run for pull requests - #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } # Build the module ./build.ps1 -ResolveDependency -tasks build @@ -51,11 +51,11 @@ build_script: # DEBUG: Comment and un-comment integration tests as needed for the purpose of debugging. # Note that some integration tests depend on each other to work. See the README for more # information: https://github.com/dsccommunity/SqlServerDsc/blob/main/tests/Integration/README.md -# DEBUG: If running on own AppVeyor project on-comment the line below that skips if it is not a pull request +# DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request test_script: - ps: | # Only run for pull requests - #if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $env:TEST_CONFIGURATION -PesterPath @( ### Run the integration tests in a specific group order. @@ -66,7 +66,7 @@ test_script: #'tests/Integration/DSC_SqlLogin.Integration.Tests.ps1' #'tests/Integration/DSC_SqlEndpoint.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseMail.Integration.Tests.ps1' - #'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseDefaultLocation.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1' #'tests/Integration/DSC_SqlAlwaysOnService.Integration.Tests.ps1' @@ -75,10 +75,10 @@ test_script: #'tests/Integration/DSC_SqlAgentFailsafe.Integration.Tests.ps1' ## Group 3 #'tests/Integration/DSC_SqlRole.Integration.Tests.ps1' - #'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' #'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' + #'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' ## Group 4 #'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' #'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' @@ -96,7 +96,10 @@ test_script: deploy: off # DEBUG: Un-comment the line "$blockRdp = $true" so that build worker is kept up all of the 60 minutes. +# DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request on_finish: - ps: | + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + #$blockRdp = $true iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 76fba6d6312e90c0c98e5808bf180648d7228d0b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 10:09:35 +0200 Subject: [PATCH 60/71] Fix appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index cc95c4844..966c6df04 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,7 +27,7 @@ environment: init: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } else { 'Not a pull request, skipping.'} + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } else { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'} iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 37f80bbcf14a784f256d2395961fe909d01b1391 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 10:10:40 +0200 Subject: [PATCH 61/71] Fix appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 966c6df04..5e66334bd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,7 +27,7 @@ environment: init: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } else { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'} + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 2c9266d58cde7ffc78068e9274344f2b862cd35b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 10:12:46 +0200 Subject: [PATCH 62/71] Fix appveyor.yml --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5e66334bd..ecc4ad757 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,7 +35,7 @@ init: install: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } winrm quickconfig -quiet @@ -43,7 +43,7 @@ install: build_script: - pwsh: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } # Build the module ./build.ps1 -ResolveDependency -tasks build @@ -55,7 +55,7 @@ build_script: test_script: - ps: | # Only run for pull requests - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $env:TEST_CONFIGURATION -PesterPath @( ### Run the integration tests in a specific group order. @@ -99,7 +99,7 @@ deploy: off # DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request on_finish: - ps: | - if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { return } + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } #$blockRdp = $true iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) From 8b00db8c3cc8ab51b83282e08859de331d240b00 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 16:12:16 +0200 Subject: [PATCH 63/71] Add unit tests --- tests/Unit/Classes/SqlResourceBase.Tests.ps1 | 134 +++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 tests/Unit/Classes/SqlResourceBase.Tests.ps1 diff --git a/tests/Unit/Classes/SqlResourceBase.Tests.ps1 b/tests/Unit/Classes/SqlResourceBase.Tests.ps1 new file mode 100644 index 000000000..d46646ffd --- /dev/null +++ b/tests/Unit/Classes/SqlResourceBase.Tests.ps1 @@ -0,0 +1,134 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'SqlResourceBase' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [SqlResourceBase]::new() } | Should -Not -Throw + } + } + + It 'Should have a default constructor' { + InModuleScope -ScriptBlock { + $instance = [SqlResourceBase]::new() + $instance | Should -Not -BeNullOrEmpty + $instance.SqlServerObject | Should -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [SqlResourceBase]::new() + $instance.GetType().Name | Should -Be 'SqlResourceBase' + } + } + } +} + +Describe 'SqlResourceBase\GetServerObject()' -Tag 'GetServerObject' { + Context 'When a server object does not exist' { + BeforeAll { + $mockSqlResourceBaseInstance = InModuleScope -ScriptBlock { + [SqlResourceBase]::new() + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + } + + It 'Should call the correct mock' { + $result = $mockSqlResourceBaseInstance.GetServerObject() + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Server' + + Should -Invoke -CommandName Connect-SqlDscDatabaseEngine -Exactly -Times 1 -Scope It + } + + Context 'When property Credential is used' { + BeforeAll { + $mockSqlResourceBaseInstance = InModuleScope -ScriptBlock { + [SqlResourceBase]::new() + } + + $mockSqlResourceBaseInstance.Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + } + + It 'Should call the correct mock' { + $result = $mockSqlResourceBaseInstance.GetServerObject() + + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Server' + + Should -Invoke -CommandName Connect-SqlDscDatabaseEngine -ParameterFilter { + $PesterBoundParameters.Keys -contains 'Credential' + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When a server object already exist' { + BeforeAll { + $mockSqlResourceBaseInstance = InModuleScope -ScriptBlock { + [SqlResourceBase]::new() + } + + $mockSqlResourceBaseInstance.SqlServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Mock -CommandName Connect-SqlDscDatabaseEngine + } + + It 'Should call the correct mock' { + $result = $mockSqlResourceBaseInstance.GetServerObject() + $result | Should -BeOfType 'Microsoft.SqlServer.Management.Smo.Server' + + Should -Invoke -CommandName Connect-SqlDscDatabaseEngine -Exactly -Times 0 -Scope It + } + } +} From c8f65de81ae887361e472760d80de9725f395883 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 16:12:49 +0200 Subject: [PATCH 64/71] Add error logic to Modify() --- source/Classes/020.SqlAudit.ps1 | 25 +++++++++++- source/en-US/SqlAudit.strings.psd1 | 1 + tests/Integration/DSC_SqlAudit.config.ps1 | 1 - tests/Unit/Classes/SqlAudit.Tests.ps1 | 48 +++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index b34daf6d6..8f8c57125 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -379,7 +379,30 @@ class SqlAudit : SqlResourceBase # TODO: Should evaluate if LogType is assigned and DestinationType is File it should recreate if Force is $true - # TODO: Should evaluate DestinationType so that is does not try to set File properties when type is Log (and LogType and Path is not also present) + <# + Should evaluate DestinationType so that is does not try to set a + File audit property when audit type is of a Log-type. + #> + if ($null -ne $auditObject.DestinationType -and $auditObject.DestinationType -ne 'File') + { + # Look for file audit properties not in desired state + $fileAuditProperty = $properties.Keys.Where({ + $_ -in @( + 'Path' + 'MaximumFiles' + 'MaximumFileSize' + 'MaximumFileSizeUnit' + 'MaximumRolloverFiles' + 'ReserveDiskSpace' + ) + }) + + # If a property was found, throw an exception. + if ($fileAuditProperty.Count -gt 0) + { + New-InvalidOperationException -Message ($this.localizedData.AuditOfWrongTypeForUseWithProperty -f $auditObject.DestinationType) + } + } # Get all optional properties that has an assigned value. $assignedOptionalDscProperties = $this | Get-DscProperty -HasValue -Type 'Optional' -ExcludeName @( diff --git a/source/en-US/SqlAudit.strings.psd1 b/source/en-US/SqlAudit.strings.psd1 index 556b6be31..028538849 100644 --- a/source/en-US/SqlAudit.strings.psd1 +++ b/source/en-US/SqlAudit.strings.psd1 @@ -16,4 +16,5 @@ ConvertFrom-StringData @' MaximumFileSizeValueInvalid = The maximum file size must be set to a value of 0 or a value between 2 and 2147483647. (SA0004) QueueDelayValueInvalid = The queue delay must be set to a value of 0 or a value between 1000 and 2147483647. CannotCreateNewAudit = Cannot create a new audit because neither of the properties LogType or Path is specified. One of those properties must be specified to create a new audit. + AuditOfWrongTypeForUseWithProperty = A property that is not in desired state is not compatible with the audit type '{0}'. '@ diff --git a/tests/Integration/DSC_SqlAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 index 06928a0ae..ec2af48e1 100644 --- a/tests/Integration/DSC_SqlAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -152,7 +152,6 @@ Configuration DSC_SqlAudit_RemoveAudit1_Config } } - <# .SYNOPSIS Removes the log audit. diff --git a/tests/Unit/Classes/SqlAudit.Tests.ps1 b/tests/Unit/Classes/SqlAudit.Tests.ps1 index fed88d7b1..1d7ef5c5d 100644 --- a/tests/Unit/Classes/SqlAudit.Tests.ps1 +++ b/tests/Unit/Classes/SqlAudit.Tests.ps1 @@ -1026,6 +1026,54 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { } } } + + Context 'When trying to change a File audit property when audit type is of a Log-type' { + BeforeAll { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + MaximumFiles = 20 + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + + $mockAuditObject.DestinationType = 'SecurityLog' + + return $mockAuditObject + } + + Mock -CommandName Set-SqlDscAudit -RemoveParameterValidation 'Path' + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $mockErrorMessage = Get-InvalidOperationRecord -Message ( + $mockSqlAuditInstance.localizedData.AuditOfWrongTypeForUseWithProperty -f 'SecurityLog' + ) + + { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + MaximumFiles = 20 + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } } } From 75d5126d2ea37efc17d47822033be0e4d82d6501 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 17:31:15 +0200 Subject: [PATCH 65/71] Fix re-creation --- source/Classes/020.SqlAudit.ps1 | 97 ++++++++++++++++++++++-------- source/en-US/SqlAudit.strings.psd1 | 1 + 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index 8f8c57125..5353d0911 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -325,29 +325,7 @@ class SqlAudit : SqlResourceBase { 'Present' { - # Get all properties that has an assigned value. - $assignedDscProperties = $this | Get-DscProperty -HasValue -Type @( - 'Key' - 'Optional' - ) -ExcludeName @( - # Remove properties that is not an audit property. - 'InstanceName' - 'ServerName' - 'Ensure' - 'Force' - 'Credential' - - # Remove this audit property since it must be handled later. - 'Enabled' - ) - - if ($assignedDscProperties.Keys -notcontains 'LogType' -and $assignedDscProperties.Keys -notcontains 'Path') - { - New-InvalidOperationException -Message $this.localizedData.CannotCreateNewAudit - } - - # Create the audit since it was missing. Always created disabled. - $auditObject = $serverObject | New-SqlDscAudit @assignedDscProperties -Force -PassThru + $auditObject = $this.CreateAudit() } 'Absent' @@ -373,12 +351,43 @@ class SqlAudit : SqlResourceBase $auditObject = $serverObject | Get-SqlDscAudit -Name $this.Name -ErrorAction 'Stop' + <# + This logic need to be here in a separate block since we might + get an audit object back from the re-creation that then will + be used in the next if-block. + #> if ($auditObject) { - # TODO: Should evaluate if Path is assigned and DestinationType is *Log it should recreate if Force is $true + # Does the audit need to be re-created? + $auditIsWrongType = ( + # If $auditObject.DestinationType is not null. + $null -ne $auditObject.DestinationType -and ( + # Path is not in desired state but the audit is not of type File. + $properties.ContainsKey('Path') -and $auditObject.DestinationType -ne 'File' + ) -or ( + # LogType is not in desired state but the audit is of type File. + $properties.ContainsKey('LogType') -and $auditObject.DestinationType -eq 'File' + ) + ) + + if ($auditIsWrongType) + { + if ($this.Force -eq $true) + { + $auditObject | Remove-SqlDscAudit -Force - # TODO: Should evaluate if LogType is assigned and DestinationType is File it should recreate if Force is $true + $auditObject = $this.CreateAudit() + } + else + { + New-InvalidOperationException -Message $this.localizedData.AuditIsWrongType + } + } + } + # Is it an audit object from either Get-SqlDscAudit or from re-creation? + if ($auditObject) + { <# Should evaluate DestinationType so that is does not try to set a File audit property when audit type is of a Log-type. @@ -551,4 +560,42 @@ class SqlAudit : SqlResourceBase New-InvalidArgumentException -ArgumentName 'Path' -Message $errorMessage } } + + <# + Create and returns the desired audit object. + + This should return an object of type [Microsoft.SqlServer.Management.Smo.Audit] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] CreateAudit() + { + # Get all properties that has an assigned value. + $assignedDscProperties = $this | Get-DscProperty -HasValue -Type @( + 'Key' + 'Optional' + ) -ExcludeName @( + # Remove properties that is not an audit property. + 'InstanceName' + 'ServerName' + 'Ensure' + 'Force' + 'Credential' + + # Remove this audit property since it must be handled later. + 'Enabled' + ) + + if ($assignedDscProperties.Keys -notcontains 'LogType' -and $assignedDscProperties.Keys -notcontains 'Path') + { + New-InvalidOperationException -Message $this.localizedData.CannotCreateNewAudit + } + + $serverObject = $this.GetServerObject() + + # Create the audit since it was missing. Always created disabled. + $auditObject = $serverObject | New-SqlDscAudit @assignedDscProperties -Force -PassThru + + return $auditObject + } } diff --git a/source/en-US/SqlAudit.strings.psd1 b/source/en-US/SqlAudit.strings.psd1 index 028538849..bcc6414f6 100644 --- a/source/en-US/SqlAudit.strings.psd1 +++ b/source/en-US/SqlAudit.strings.psd1 @@ -17,4 +17,5 @@ ConvertFrom-StringData @' QueueDelayValueInvalid = The queue delay must be set to a value of 0 or a value between 1000 and 2147483647. CannotCreateNewAudit = Cannot create a new audit because neither of the properties LogType or Path is specified. One of those properties must be specified to create a new audit. AuditOfWrongTypeForUseWithProperty = A property that is not in desired state is not compatible with the audit type '{0}'. + AuditIsWrongType = The existing audit is of wrong type to be able to update the property that is not in desired state. If the audit should be re-created set Force to $true. '@ From 7e438d9c8e18d115625b449d84c99a2d9c469ad2 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 17:53:54 +0200 Subject: [PATCH 66/71] Fix re-creation with tests --- source/Classes/020.SqlAudit.ps1 | 105 +++++++-------- tests/Unit/Classes/SqlAudit.Tests.ps1 | 185 +++++++++++++++++++++++++- 2 files changed, 228 insertions(+), 62 deletions(-) diff --git a/source/Classes/020.SqlAudit.ps1 b/source/Classes/020.SqlAudit.ps1 index 5353d0911..a0ac2bc44 100644 --- a/source/Classes/020.SqlAudit.ps1 +++ b/source/Classes/020.SqlAudit.ps1 @@ -325,6 +325,7 @@ class SqlAudit : SqlResourceBase { 'Present' { + # Create the audit since it was missing. Always created disabled. $auditObject = $this.CreateAudit() } @@ -351,14 +352,8 @@ class SqlAudit : SqlResourceBase $auditObject = $serverObject | Get-SqlDscAudit -Name $this.Name -ErrorAction 'Stop' - <# - This logic need to be here in a separate block since we might - get an audit object back from the re-creation that then will - be used in the next if-block. - #> if ($auditObject) { - # Does the audit need to be re-created? $auditIsWrongType = ( # If $auditObject.DestinationType is not null. $null -ne $auditObject.DestinationType -and ( @@ -370,6 +365,7 @@ class SqlAudit : SqlResourceBase ) ) + # Does the audit need to be re-created? if ($auditIsWrongType) { if ($this.Force -eq $true) @@ -383,62 +379,60 @@ class SqlAudit : SqlResourceBase New-InvalidOperationException -Message $this.localizedData.AuditIsWrongType } } - } - - # Is it an audit object from either Get-SqlDscAudit or from re-creation? - if ($auditObject) - { - <# - Should evaluate DestinationType so that is does not try to set a - File audit property when audit type is of a Log-type. - #> - if ($null -ne $auditObject.DestinationType -and $auditObject.DestinationType -ne 'File') + else { - # Look for file audit properties not in desired state - $fileAuditProperty = $properties.Keys.Where({ - $_ -in @( - 'Path' - 'MaximumFiles' - 'MaximumFileSize' - 'MaximumFileSizeUnit' - 'MaximumRolloverFiles' - 'ReserveDiskSpace' - ) - }) - - # If a property was found, throw an exception. - if ($fileAuditProperty.Count -gt 0) + <# + Should evaluate DestinationType so that is does not try to set a + File audit property when audit type is of a Log-type. + #> + if ($null -ne $auditObject.DestinationType -and $auditObject.DestinationType -ne 'File') { - New-InvalidOperationException -Message ($this.localizedData.AuditOfWrongTypeForUseWithProperty -f $auditObject.DestinationType) + # Look for file audit properties not in desired state + $fileAuditProperty = $properties.Keys.Where({ + $_ -in @( + 'Path' + 'MaximumFiles' + 'MaximumFileSize' + 'MaximumFileSizeUnit' + 'MaximumRolloverFiles' + 'ReserveDiskSpace' + ) + }) + + # If a property was found, throw an exception. + if ($fileAuditProperty.Count -gt 0) + { + New-InvalidOperationException -Message ($this.localizedData.AuditOfWrongTypeForUseWithProperty -f $auditObject.DestinationType) + } } - } - # Get all optional properties that has an assigned value. - $assignedOptionalDscProperties = $this | Get-DscProperty -HasValue -Type 'Optional' -ExcludeName @( - # Remove optional properties that is not an audit property. - 'ServerName' - 'Ensure' - 'Force' - 'Credential' + # Get all optional properties that has an assigned value. + $assignedOptionalDscProperties = $this | Get-DscProperty -HasValue -Type 'Optional' -ExcludeName @( + # Remove optional properties that is not an audit property. + 'ServerName' + 'Ensure' + 'Force' + 'Credential' - # Remove this audit property since it must be handled later. - 'Enabled' - ) + # Remove this audit property since it must be handled later. + 'Enabled' + ) - <# - Only call Set when there is a property to Set. The property - Enabled was ignored, so it could be the only one that was - not in desired state (Enabled is handled later). - #> - if ($assignedOptionalDscProperties.Count -gt 0) - { <# - This calls Set-SqlDscAudit to set all the desired value - even if they were in desired state. Then the no logic is - needed to make sure we call using the correct parameter - set that Set-SqlDscAudit requires. + Only call Set when there is a property to Set. The property + Enabled was ignored, so it could be the only one that was + not in desired state (Enabled is handled later). #> - $auditObject | Set-SqlDscAudit @assignedOptionalDscProperties -Force + if ($assignedOptionalDscProperties.Count -gt 0) + { + <# + This calls Set-SqlDscAudit to set all the desired value + even if they were in desired state. Then the no logic is + needed to make sure we call using the correct parameter + set that Set-SqlDscAudit requires. + #> + $auditObject | Set-SqlDscAudit @assignedOptionalDscProperties -Force + } } } } @@ -562,7 +556,7 @@ class SqlAudit : SqlResourceBase } <# - Create and returns the desired audit object. + Create and returns the desired audit object. Always created disabled. This should return an object of type [Microsoft.SqlServer.Management.Smo.Audit] but using that type fails the build process currently. @@ -593,7 +587,6 @@ class SqlAudit : SqlResourceBase $serverObject = $this.GetServerObject() - # Create the audit since it was missing. Always created disabled. $auditObject = $serverObject | New-SqlDscAudit @assignedDscProperties -Force -PassThru return $auditObject diff --git a/tests/Unit/Classes/SqlAudit.Tests.ps1 b/tests/Unit/Classes/SqlAudit.Tests.ps1 index 1d7ef5c5d..c9cfa02d9 100644 --- a/tests/Unit/Classes/SqlAudit.Tests.ps1 +++ b/tests/Unit/Classes/SqlAudit.Tests.ps1 @@ -897,7 +897,7 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { Context 'When the property MaximumFileSize is not in desired state' { BeforeAll { - InModuleScope -Parameters $_ -ScriptBlock { + InModuleScope -ScriptBlock { $script:mockSqlAuditInstance = [SqlAudit] @{ Name = 'MockAuditName' InstanceName = 'NamedInstance' @@ -923,7 +923,7 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { } It 'Should call the correct mocks' { - InModuleScope -Parameters $_ -ScriptBlock { + InModuleScope -ScriptBlock { $script:mockSqlAuditInstance.Modify( # This is the properties not in desired state. @{ @@ -941,7 +941,7 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { Context 'When the property MaximumFileSizeUnit is not in desired state' { BeforeAll { - InModuleScope -Parameters $_ -ScriptBlock { + InModuleScope -ScriptBlock { $script:mockSqlAuditInstance = [SqlAudit] @{ Name = 'MockAuditName' InstanceName = 'NamedInstance' @@ -967,7 +967,7 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { } It 'Should call the correct mocks' { - InModuleScope -Parameters $_ -ScriptBlock { + InModuleScope -ScriptBlock { $script:mockSqlAuditInstance.Modify( # This is the properties not in desired state. @{ @@ -985,7 +985,7 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { Context 'When the property ReservDiskSpace is not in desired state' { BeforeAll { - InModuleScope -Parameters $_ -ScriptBlock { + InModuleScope -ScriptBlock { $script:mockSqlAuditInstance = [SqlAudit] @{ Name = 'MockAuditName' InstanceName = 'NamedInstance' @@ -1029,7 +1029,7 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { Context 'When trying to change a File audit property when audit type is of a Log-type' { BeforeAll { - InModuleScope -Parameters $_ -ScriptBlock { + InModuleScope -ScriptBlock { $script:mockSqlAuditInstance = [SqlAudit] @{ Name = 'MockAuditName' InstanceName = 'NamedInstance' @@ -1074,6 +1074,179 @@ Describe 'SqlAudit\Modify()' -Tag 'Modify' { } } } + + Context 'When trying to change Path but audit type is of a Log-type' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + Force = $true + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'CreateAudit' -Value { + $script:mockMethodCreateAuditCallCount += 1 + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + + $mockAuditObject.DestinationType = 'SecurityLog' + + return $mockAuditObject + } + + Mock -CommandName Remove-SqlDscAudit + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodCreateAuditCallCount = 0 + } + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Path = 'C:\Temp' + } + ) + + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 1 -Scope It + + $script:mockMethodCreateAuditCallCount | Should -Be 1 + } + } + } + + Context 'When trying to change LogType but audit type is a File-type' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + LogType = 'ApplicationLog' + Force = $true + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'CreateAudit' -Value { + $script:mockMethodCreateAuditCallCount += 1 + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + + $mockAuditObject.DestinationType = 'File' + + return $mockAuditObject + } + + Mock -CommandName Remove-SqlDscAudit + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodCreateAuditCallCount = 0 + } + } + + It 'Should call the correct mocks' { + InModuleScope -Parameters $_ -ScriptBlock { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + LogType = 'ApplicationLog' + } + ) + + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 1 -Scope It + + $script:mockMethodCreateAuditCallCount | Should -Be 1 + } + } + } + + Context 'When trying to change Path but audit type is of a Log-type and Force is not set to $true' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlAuditInstance = [SqlAudit] @{ + Name = 'MockAuditName' + InstanceName = 'NamedInstance' + Path = 'C:\Temp' + } | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetServerObject' -Value { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'CreateAudit' -Value { + $script:mockMethodCreateAuditCallCount += 1 + } -PassThru + } + + Mock -CommandName Get-SqlDscAudit -MockWith { + $mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server'), + 'MockAuditName' + ) + + $mockAuditObject.DestinationType = 'SecurityLog' + + return $mockAuditObject + } + + Mock -CommandName Remove-SqlDscAudit + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodCreateAuditCallCount = 0 + } + } + + It 'Should throw the correct error' { + InModuleScope -Parameters $_ -ScriptBlock { + $mockErrorMessage = Get-InvalidOperationRecord -Message ( + $mockSqlAuditInstance.localizedData.AuditIsWrongType + ) + + { + $script:mockSqlAuditInstance.Modify( + # This is the properties not in desired state. + @{ + Path = 'C:\Temp' + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 0 -Scope It + + $script:mockMethodCreateAuditCallCount | Should -Be 0 + } + } + } } } From bfdecd1c7476cdde5aa3323a461a820b89afd831 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 10 Aug 2022 20:12:57 +0200 Subject: [PATCH 67/71] Fix test task to convert code coverage --- build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/build.yaml b/build.yaml index 40e6d57a3..23bd2c55c 100644 --- a/build.yaml +++ b/build.yaml @@ -24,6 +24,7 @@ BuildWorkflow: test: - Pester_Tests_Stop_On_Fail + - Convert_Pester_Coverage - Pester_If_Code_Coverage_Under_Threshold publish: From 8e23227a96d569c5a200a565da68f58aa88bcc9e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 11 Aug 2022 13:47:00 +0200 Subject: [PATCH 68/71] Remove comment --- tests/Integration/DSC_SqlAudit.config.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Integration/DSC_SqlAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 index ec2af48e1..d68f28b72 100644 --- a/tests/Integration/DSC_SqlAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -43,8 +43,6 @@ else ) } - # TODO: This leaves the SecLogAudit, if so it should be documented. - # TODO: This folder should be created with DSC. New-Item -Path 'C:\Temp\audit' -ItemType 'Directory' -Force | Out-Null } From d37995a7dddc67a4f694af207f6f8ed4b1c0ff5c Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 11 Aug 2022 13:59:30 +0200 Subject: [PATCH 69/71] Fix integ tests --- .../DSC_SqlAudit.Integration.Tests.ps1 | 35 +++++++++++++++++++ tests/Integration/DSC_SqlAudit.config.ps1 | 21 +++++++++-- tests/Integration/README.md | 6 ++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 index 3ce8652af..1a8801404 100644 --- a/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlAudit.Integration.Tests.ps1 @@ -41,6 +41,41 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" } + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_Prerequisites_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } + Context ('When using configuration <_>') -ForEach @( "$($script:dscResourceName)_AddFileAudit_Config" ) { diff --git a/tests/Integration/DSC_SqlAudit.config.ps1 b/tests/Integration/DSC_SqlAudit.config.ps1 index d68f28b72..6c650220c 100644 --- a/tests/Integration/DSC_SqlAudit.config.ps1 +++ b/tests/Integration/DSC_SqlAudit.config.ps1 @@ -42,9 +42,26 @@ else } ) } +} + +<# + .SYNOPSIS + Creates a folder that is needed for creating a File audit. +#> +Configuration DSC_SqlAudit_Prerequisites_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' - # TODO: This folder should be created with DSC. - New-Item -Path 'C:\Temp\audit' -ItemType 'Directory' -Force | Out-Null + node $AllNodes.NodeName + { + File 'Integration_Test' + { + Ensure = 'Present' + DestinationPath = $Node.Path1 + Type = 'Directory' + Force = $true + } + } } <# diff --git a/tests/Integration/README.md b/tests/Integration/README.md index 964286755..d6bbe1cba 100644 --- a/tests/Integration/README.md +++ b/tests/Integration/README.md @@ -454,6 +454,12 @@ This integration tests depends on the named instance `DSCSQLTEST`. The integration test will not leave anything on any instance. +The integration tests will leave a created path on the filesystem: + +Path | +--- | +C\Temp\audit | + ### SqlScript **Run order:** 4 From 478628cf3ba2a81fa7d3841e7d0c039a8ebf0178 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Thu, 11 Aug 2022 14:24:10 +0200 Subject: [PATCH 70/71] Fix remove GLobals class in SMO stubs --- tests/Unit/Stubs/SMO.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index c210614d2..1f21e9cea 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -159,12 +159,6 @@ public enum SqlSmoState : int #region Public Classes - public class Globals - { - // Static property that is switched on or off by tests if data should be mocked (true) or not (false). - public static bool GenerateMockData = false; - } - // Typename: Microsoft.SqlServer.Management.Smo.ObjectPermissionSet // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase // Used by: From 27071678b353940d7e4209f821165fb82b33eeb7 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 13 Aug 2022 06:57:35 +0200 Subject: [PATCH 71/71] FIx review comment --- source/en-US/SqlAudit.strings.psd1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/en-US/SqlAudit.strings.psd1 b/source/en-US/SqlAudit.strings.psd1 index bcc6414f6..8a818d13b 100644 --- a/source/en-US/SqlAudit.strings.psd1 +++ b/source/en-US/SqlAudit.strings.psd1 @@ -14,8 +14,8 @@ ConvertFrom-StringData @' PathInvalid = The path '{0}' does not exist. Audit file can only be created in a path that already exist and where the SQL Server instance has permission to write. (SA0003) EvaluateServerAudit = Evaluate the current audit '{0}' on the instance '{1}'. (SA0004) MaximumFileSizeValueInvalid = The maximum file size must be set to a value of 0 or a value between 2 and 2147483647. (SA0004) - QueueDelayValueInvalid = The queue delay must be set to a value of 0 or a value between 1000 and 2147483647. - CannotCreateNewAudit = Cannot create a new audit because neither of the properties LogType or Path is specified. One of those properties must be specified to create a new audit. - AuditOfWrongTypeForUseWithProperty = A property that is not in desired state is not compatible with the audit type '{0}'. - AuditIsWrongType = The existing audit is of wrong type to be able to update the property that is not in desired state. If the audit should be re-created set Force to $true. + QueueDelayValueInvalid = The queue delay must be set to a value of 0 or a value between 1000 and 2147483647. (SA0005) + CannotCreateNewAudit = Cannot create a new audit because neither of the properties LogType or Path is specified. One of those properties must be specified to create a new audit. (SA0006) + AuditOfWrongTypeForUseWithProperty = A property that is not in desired state is not compatible with the audit type '{0}'. (SA0007) + AuditIsWrongType = The existing audit is of wrong type to be able to update the property that is not in desired state. If the audit should be re-created set Force to $true. (SA0008) '@