diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a18d0d121..9b9197c74a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added integration tests for `Add-SqlDscTraceFlag` command to ensure it functions correctly in real environments [issue #2214](https://github.com/dsccommunity/SqlServerDsc/issues/2214). +- `Set-SqlDscAudit` + - Added `AllowAuditGuidChange` parameter to enable modifying the audit GUID + by dropping and recreating the audit with the new GUID. This parameter is + required when changing the `AuditGuid` property because SQL Server does not + allow direct modification of the audit GUID ([issue #2287](https://github.com/dsccommunity/SqlServerDsc/issues/2287)). ### Changed diff --git a/source/Private/ConvertTo-AuditNewParameterSet.ps1 b/source/Private/ConvertTo-AuditNewParameterSet.ps1 new file mode 100644 index 0000000000..dc09076fa3 --- /dev/null +++ b/source/Private/ConvertTo-AuditNewParameterSet.ps1 @@ -0,0 +1,144 @@ +<# + .SYNOPSIS + Converts audit object properties to parameters for New-SqlDscAudit. + + .DESCRIPTION + This helper function analyzes an existing audit object and returns a hashtable + of parameters that can be splatted to New-SqlDscAudit to recreate the audit + with the same configuration. + + .PARAMETER AuditObject + The audit object to analyze. + + .PARAMETER AuditGuid + Optional GUID to set on the audit. If not specified, the existing GUID is used. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + $auditObject = $serverObject.Audits['MyAudit'] + $parameters = ConvertTo-AuditNewParameterSet -AuditObject $auditObject + + Converts an existing audit object to a parameter set that can be used with New-SqlDscAudit. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine + $auditObject = $serverObject.Audits['MyAudit'] + $parameters = ConvertTo-AuditNewParameterSet -AuditObject $auditObject -AuditGuid '12345678-1234-1234-1234-123456789012' + + Converts an existing audit object to a parameter set with a custom GUID. + + .INPUTS + None + + This function does not accept pipeline input. + + .OUTPUTS + System.Collections.Hashtable + + Returns a hashtable of parameters for New-SqlDscAudit. +#> +function ConvertTo-AuditNewParameterSet +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Audit] + $AuditObject, + + [Parameter()] + [System.String] + $AuditGuid + ) + + $parameters = @{ + ServerObject = $AuditObject.Parent + Name = $AuditObject.Name + } + + # Determine LogType or Path based on DestinationType + switch ($AuditObject.DestinationType) + { + 'ApplicationLog' + { + $parameters['LogType'] = 'ApplicationLog' + } + + 'SecurityLog' + { + $parameters['LogType'] = 'SecurityLog' + } + + 'File' + { + $parameters['Path'] = $AuditObject.FilePath + + # Add file size parameters if set (not unlimited) + if ($AuditObject.MaximumFileSize -gt 0) + { + $parameters['MaximumFileSize'] = $AuditObject.MaximumFileSize + + # Convert SMO unit to parameter value + $parameters['MaximumFileSizeUnit'] = switch ($AuditObject.MaximumFileSizeUnit) + { + 'MB' + { + 'Megabyte' + } + 'GB' + { + 'Gigabyte' + } + 'TB' + { + 'Terabyte' + } + } + } + + # Add MaximumFiles or MaximumRolloverFiles (mutually exclusive) + if ($AuditObject.MaximumFiles -gt 0) + { + $parameters['MaximumFiles'] = $AuditObject.MaximumFiles + + if ($AuditObject.ReserveDiskSpace) + { + $parameters['ReserveDiskSpace'] = $true + } + } + elseif ($AuditObject.MaximumRolloverFiles -gt 0) + { + $parameters['MaximumRolloverFiles'] = $AuditObject.MaximumRolloverFiles + } + } + } + + # Add optional parameters if they have values + if ($null -ne $AuditObject.OnFailure) + { + $parameters['OnFailure'] = $AuditObject.OnFailure + } + + if ($AuditObject.QueueDelay -gt 0) + { + $parameters['QueueDelay'] = $AuditObject.QueueDelay + } + + if ($AuditObject.Filter) + { + $parameters['AuditFilter'] = $AuditObject.Filter + } + + # Use provided GUID or existing GUID + if ($PSBoundParameters.ContainsKey('AuditGuid')) + { + $parameters['AuditGuid'] = $AuditGuid + } + elseif ($AuditObject.Guid -and $AuditObject.Guid -ne '00000000-0000-0000-0000-000000000000') + { + $parameters['AuditGuid'] = $AuditObject.Guid + } + + return $parameters +} diff --git a/source/Public/Set-SqlDscAudit.ps1 b/source/Public/Set-SqlDscAudit.ps1 index 6c17788031..6b1649b370 100644 --- a/source/Public/Set-SqlDscAudit.ps1 +++ b/source/Public/Set-SqlDscAudit.ps1 @@ -3,7 +3,7 @@ Updates a server audit. .DESCRIPTION - This command updates and existing server audit on a SQL Server Database Engine + This command updates an existing server audit on a SQL Server Database Engine instance. .PARAMETER ServerObject @@ -32,6 +32,13 @@ Specifies the GUID found in the mirrored database. To support scenarios such as database mirroring an audit needs a specific GUID. + .PARAMETER AllowAuditGuidChange + Specifies that the audit GUID can be changed by dropping and recreating the + audit. This parameter is required when modifying the AuditGuid property because + SQL Server does not allow direct modification of the audit GUID. When specified, + the audit will be dropped and recreated with all existing properties plus the + new GUID value. This is a destructive operation that requires explicit opt-in. + .PARAMETER Force Specifies that the audit should be updated without any confirmation. @@ -42,7 +49,7 @@ it might be better to make sure the ServerObject is recent enough. .PARAMETER Path - Specifies the location where te log files wil be placed. + Specifies the location where the log files will be placed. .PARAMETER ReserveDiskSpace Specifies if the needed file space should be reserved. To use this parameter @@ -57,7 +64,8 @@ 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 used for the file size. This can be Megabyte, Gigabyte, + or Terabyte. .PARAMETER MaximumRolloverFiles Specifies the amount of files on disk before SQL Server starts reusing @@ -72,25 +80,31 @@ .EXAMPLE $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' - $sqlServerObject | Set-SqlDscAudit -Name 'MyFileAudit' -Path 'E:\auditFolder' -QueueDelay 1000 + $serverObject | Set-SqlDscAudit -Name 'MyFileAudit' -Path 'E:\auditFolder' -QueueDelay 1000 - Updates the file audit named **MyFileAudit** by setting the path to ''E:\auditFolder' + 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 + $serverObject | 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 + $serverObject | 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. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Set-SqlDscAudit -Name 'MyFileAudit' -AuditGuid '12345678-1234-1234-1234-123456789012' -AllowAuditGuidChange + + Changes the audit GUID by dropping and recreating the audit with the specified GUID. + .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. @@ -158,6 +172,10 @@ function Set-SqlDscAudit [System.String] $AuditGuid, + [Parameter()] + [System.Management.Automation.SwitchParameter] + $AllowAuditGuidChange, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force, @@ -250,7 +268,7 @@ function Set-SqlDscAudit $ConfirmPreference = 'None' } - if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + if ($PSCmdlet.ParameterSetName -match '^ServerObject') { $getSqlDscAuditParameters = @{ ServerObject = $ServerObject @@ -271,85 +289,139 @@ function Set-SqlDscAudit $AuditObject.Refresh() } - $verboseDescriptionMessage = $script:localizedData.Audit_Update_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName - $verboseWarningMessage = $script:localizedData.Audit_Update_ShouldProcessVerboseWarning -f $AuditObject.Name + $descriptionMessage = $script:localizedData.Audit_Update_ShouldProcessVerboseDescription -f $AuditObject.Name, $AuditObject.Parent.InstanceName + $confirmationMessage = $script:localizedData.Audit_Update_ShouldProcessVerboseWarning -f $AuditObject.Name $captionMessage = $script:localizedData.Audit_Update_ShouldProcessCaption - if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) { - if ($PSBoundParameters.ContainsKey('Path')) + # Check if GUID change is requested early, before any other modifications + if ($PSBoundParameters.ContainsKey('AuditGuid') -and $AuditObject.Guid -ne $AuditGuid) { - $AuditObject.FilePath = $Path - } + # Validate that AllowAuditGuidChange is present + if (-not $AllowAuditGuidChange.IsPresent) + { + $errorMessage = $script:localizedData.Audit_AuditGuidChangeRequiresAllowParameter -f $AuditObject.Name + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $errorMessage, + 'SSDA0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $AuditObject.Name + ) + ) + } - if ($PSCmdlet.ParameterSetName -match 'WithSize') - { - $queryMaximumFileSizeUnit = ( - @{ - Megabyte = 'MB' - Gigabyte = 'GB' - Terabyte = 'TB' - } - ).$MaximumFileSizeUnit + Write-Debug -Message ($script:localizedData.Audit_RecreatingAuditForGuidChange -f $AuditObject.Name, $AuditObject.Parent.InstanceName, $AuditGuid) - $AuditObject.MaximumFileSize = $MaximumFileSize - $AuditObject.MaximumFileSizeUnit = $queryMaximumFileSizeUnit - } + # Convert audit properties to parameters for New-SqlDscAudit + $newAuditParameters = ConvertTo-AuditNewParameterSet -AuditObject $AuditObject -AuditGuid $AuditGuid - if ($PSCmdlet.ParameterSetName -match 'MaxFiles') - { - if ($AuditObject.MaximumRolloverFiles) + # Drop the existing audit using Remove-SqlDscAudit + # Use -Confirm:$false since we're already in a confirmed ShouldProcess context + $AuditObject | Remove-SqlDscAudit -Confirm:$false + + # Create new audit with the same properties using New-SqlDscAudit + # Use -Confirm:$false and -PassThru since we're already in a confirmed context and need the object + $AuditObject = New-SqlDscAudit @newAuditParameters -Confirm:$false -PassThru + + # Remove parameters that should not be passed to recursive call + $null = $PSBoundParameters.Remove('AuditGuid') + $null = $PSBoundParameters.Remove('AllowAuditGuidChange') + $null = $PSBoundParameters.Remove('ServerObject') + $null = $PSBoundParameters.Remove('Name') + $null = $PSBoundParameters.Remove('Force') + $null = $PSBoundParameters.Remove('Refresh') + $null = $PSBoundParameters.Remove('PassThru') + + # Remove common parameters and get the resulting hashtable + $remainingParameters = Remove-CommonParameter -Hashtable $PSBoundParameters + + # If there are other property-changing parameters to apply, recursively call Set-SqlDscAudit + if ($remainingParameters.Count -gt 0) { - # Switching to MaximumFiles instead of MaximumRolloverFiles. - $AuditObject.MaximumRolloverFiles = 0 + # Add the new AuditObject parameter + $remainingParameters['AuditObject'] = $AuditObject - # Must run method Alter() before setting MaximumFiles. - $AuditObject.Alter() + # Recursively call to apply remaining changes + $AuditObject = Set-SqlDscAudit @remainingParameters -Confirm:$false -PassThru } + } + else + { + # No GUID change (or GUID is the same), proceed with normal property updates - $AuditObject.MaximumFiles = $MaximumFiles + # Apply other parameter changes + if ($PSBoundParameters.ContainsKey('Path')) + { + $AuditObject.FilePath = $Path + } - if ($PSBoundParameters.ContainsKey('ReserveDiskSpace')) + if ($PSCmdlet.ParameterSetName -match 'WithSize') { - $AuditObject.ReserveDiskSpace = $ReserveDiskSpace.IsPresent + $queryMaximumFileSizeUnit = ( + @{ + Megabyte = 'MB' + Gigabyte = 'GB' + Terabyte = 'TB' + } + ).$MaximumFileSizeUnit + + $AuditObject.MaximumFileSize = $MaximumFileSize + $AuditObject.MaximumFileSizeUnit = $queryMaximumFileSizeUnit } - } - if ($PSCmdlet.ParameterSetName -match 'MaxRolloverFiles') - { - if ($AuditObject.MaximumFiles) + if ($PSCmdlet.ParameterSetName -match 'MaxFiles') { - # Switching to MaximumRolloverFiles instead of MaximumFiles. - $AuditObject.MaximumFiles = 0 + if ($AuditObject.MaximumRolloverFiles) + { + # Switching to MaximumFiles instead of MaximumRolloverFiles. + $AuditObject.MaximumRolloverFiles = 0 + + # Must run method Alter() before setting MaximumFiles. + $AuditObject.Alter() + } + + $AuditObject.MaximumFiles = $MaximumFiles - # Must run method Alter() before setting MaximumRolloverFiles. - $AuditObject.Alter() + if ($PSBoundParameters.ContainsKey('ReserveDiskSpace')) + { + $AuditObject.ReserveDiskSpace = $ReserveDiskSpace.IsPresent + } } - $AuditObject.MaximumRolloverFiles = $MaximumRolloverFiles - } + if ($PSCmdlet.ParameterSetName -match 'MaxRolloverFiles') + { + if ($AuditObject.MaximumFiles) + { + # Switching to MaximumRolloverFiles instead of MaximumFiles. + $AuditObject.MaximumFiles = 0 - if ($PSBoundParameters.ContainsKey('OnFailure')) - { - $AuditObject.OnFailure = $OnFailure - } + # Must run method Alter() before setting MaximumRolloverFiles. + $AuditObject.Alter() + } - if ($PSBoundParameters.ContainsKey('QueueDelay')) - { - $AuditObject.QueueDelay = $QueueDelay - } + $AuditObject.MaximumRolloverFiles = $MaximumRolloverFiles + } - if ($PSBoundParameters.ContainsKey('AuditGuid')) - { - $AuditObject.Guid = $AuditGuid - } + if ($PSBoundParameters.ContainsKey('OnFailure')) + { + $AuditObject.OnFailure = $OnFailure + } - if ($PSBoundParameters.ContainsKey('AuditFilter')) - { - $AuditObject.Filter = $AuditFilter - } + if ($PSBoundParameters.ContainsKey('QueueDelay')) + { + $AuditObject.QueueDelay = $QueueDelay + } + + if ($PSBoundParameters.ContainsKey('AuditFilter')) + { + $AuditObject.Filter = $AuditFilter + } - $AuditObject.Alter() + $AuditObject.Alter() + } if ($PassThru.IsPresent) { diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index c6ed3ecbf8..e59f9f27f4 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -84,6 +84,8 @@ ConvertFrom-StringData @' 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. + Audit_AuditGuidChangeRequiresAllowParameter = Cannot modify the AuditGuid property of the audit '{0}'. SQL Server does not allow direct modification of the audit GUID. Use the parameter AllowAuditGuidChange to permit dropping and recreating the audit with the new GUID. + Audit_RecreatingAuditForGuidChange = Recreating the audit '{0}' on instance '{1}' to change the audit GUID to '{2}'. ## Get-SqlDscAudit Audit_Missing = There is no audit with the name '{0}'. diff --git a/tests/Integration/Commands/Set-SqlDscAudit.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscAudit.Integration.Tests.ps1 index 8ce4488917..cc60f09b15 100644 --- a/tests/Integration/Commands/Set-SqlDscAudit.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Set-SqlDscAudit.Integration.Tests.ps1 @@ -107,22 +107,34 @@ Describe 'Set-SqlDscAudit' -Tag @('Integration_SQL2017', 'Integration_SQL2019', $modifiedAudit.Filter | Should -Be $newAuditFilter } - # It 'Should modify audit AuditGuid property successfully' { - # # Verify audit exists before modification - # $originalAudit = Get-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -ErrorAction Stop - # $originalAudit | Should -Not -BeNullOrEmpty - - # $originalGuid = $originalAudit.Guid - # $newGuid = [System.Guid]::NewGuid().ToString() - - # # Modify the audit - # $null = Set-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -AuditGuid $newGuid -Force -ErrorAction Stop - - # # Verify audit was modified - # $modifiedAudit = Get-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -ErrorAction Stop - # $modifiedAudit.Guid | Should -Be $newGuid - # $modifiedAudit.Guid | Should -Not -Be $originalGuid - # } + It 'Should modify audit AuditGuid property when AllowAuditGuidChange is specified' { + # Verify audit exists before modification + $originalAudit = Get-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -ErrorAction Stop + $originalAudit | Should -Not -BeNullOrEmpty + + $originalGuid = $originalAudit.Guid + $newGuid = [System.Guid]::NewGuid().ToString() + + # Modify the audit with AllowAuditGuidChange parameter + $null = Set-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -AuditGuid $newGuid -AllowAuditGuidChange -Force -ErrorAction Stop + + # Verify audit was modified + $modifiedAudit = Get-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -ErrorAction Stop + $modifiedAudit.Guid | Should -Be $newGuid + $modifiedAudit.Guid | Should -Not -Be $originalGuid + } + + It 'Should throw an error when trying to change AuditGuid without AllowAuditGuidChange parameter' { + # Verify audit exists before modification + $originalAudit = Get-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -ErrorAction Stop + $originalAudit | Should -Not -BeNullOrEmpty + + $newGuid = [System.Guid]::NewGuid().ToString() + + # Attempt to modify the audit GUID without AllowAuditGuidChange should throw an error + { Set-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -AuditGuid $newGuid -Force -ErrorAction Stop } | + Should -Throw + } It 'Should support multiple property modifications in one call' { # Verify audit exists before modification diff --git a/tests/Integration/Private/ConvertTo-AuditNewParameterSet.Integration.Tests.ps1 b/tests/Integration/Private/ConvertTo-AuditNewParameterSet.Integration.Tests.ps1 new file mode 100644 index 0000000000..0a211a324c --- /dev/null +++ b/tests/Integration/Private/ConvertTo-AuditNewParameterSet.Integration.Tests.ps1 @@ -0,0 +1,555 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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 noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') +} + +Describe 'ConvertTo-AuditNewParameterSet' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $script:mockInstanceName = 'DSCSQLTEST' + $script:mockComputerName = Get-ComputerName + + $mockSqlAdministratorUserName = 'SqlAdmin' + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential -ErrorAction Stop + + # Create a temporary directory for file audits if it doesn't exist + $script:testAuditPath = 'C:\Temp\SqlDscTestAudits' + if (-not (Test-Path -Path $script:testAuditPath)) + { + $null = New-Item -Path $script:testAuditPath -ItemType Directory -Force + } + } + + AfterAll { + # Clean up any test audits that might remain + $testAudits = Get-SqlDscAudit -ServerObject $script:serverObject -ErrorAction 'SilentlyContinue' | + Where-Object { $_.Name -like 'SqlDscTestConvert*' } + + foreach ($audit in $testAudits) + { + try + { + Remove-SqlDscAudit -AuditObject $audit -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore cleanup errors + } + } + + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Clean up temporary directory + if (Test-Path -Path $script:testAuditPath) + { + Remove-Item -Path $script:testAuditPath -Recurse -Force -ErrorAction 'SilentlyContinue' + } + } + + Context 'When converting an ApplicationLog audit' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_AppLog_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -LogType 'ApplicationLog' -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct parameters that can recreate the audit' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['ServerObject'] | Should -Be $script:serverObject + $parameters['Name'] | Should -Be $script:testAudit.Name + $parameters['LogType'] | Should -Be 'ApplicationLog' + $parameters.ContainsKey('Path') | Should -BeFalse + } + + It 'Should recreate the audit with the same properties using returned parameters' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.Name | Should -Be $script:testAudit.Name + $recreatedAudit.DestinationType | Should -Be $script:testAudit.DestinationType + } + } + + Context 'When converting a SecurityLog audit' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_SecLog_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -LogType 'SecurityLog' -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct parameters for SecurityLog audit' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['LogType'] | Should -Be 'SecurityLog' + $parameters.ContainsKey('Path') | Should -BeFalse + } + } + + Context 'When converting a File audit with basic properties' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_File_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -Path $script:testAuditPath -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct parameters for File audit' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['Path'].TrimEnd('\', '/') | Should -Be $script:testAuditPath.TrimEnd('\', '/') + $parameters.ContainsKey('LogType') | Should -BeFalse + } + + It 'Should recreate the File audit with the same properties' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.Name | Should -Be $script:testAudit.Name + $recreatedAudit.DestinationType | Should -Be 'File' + $recreatedAudit.FilePath.TrimEnd('\', '/') | Should -Be $script:testAuditPath.TrimEnd('\', '/') + } + } + + Context 'When converting a File audit with MaximumFileSize' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_FileSize_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -Path $script:testAuditPath -MaximumFileSize 100 -MaximumFileSizeUnit 'Megabyte' -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct file size parameters' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['MaximumFileSize'] | Should -Be 100 + $parameters['MaximumFileSizeUnit'] | Should -Be 'Megabyte' + } + + It 'Should recreate the audit with the same file size properties' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.MaximumFileSize | Should -Be 100 + $recreatedAudit.MaximumFileSizeUnit | Should -Be 'MB' + } + } + + Context 'When converting a File audit with MaximumFiles and ReserveDiskSpace' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_MaxFiles_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -Path $script:testAuditPath -MaximumFiles 10 -MaximumFileSize 50 -MaximumFileSizeUnit 'Megabyte' -ReserveDiskSpace -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct MaximumFiles and ReserveDiskSpace parameters' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['MaximumFiles'] | Should -Be 10 + $parameters['MaximumFileSize'] | Should -Be 50 + $parameters['MaximumFileSizeUnit'] | Should -Be 'Megabyte' + $parameters['ReserveDiskSpace'] | Should -BeTrue + $parameters.ContainsKey('MaximumRolloverFiles') | Should -BeFalse + } + + It 'Should recreate the audit with the same MaximumFiles properties' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.MaximumFiles | Should -Be 10 + $recreatedAudit.MaximumFileSize | Should -Be 50 + $recreatedAudit.ReserveDiskSpace | Should -BeTrue + } + } + + Context 'When converting a File audit with MaximumRolloverFiles' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_Rollover_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -Path $script:testAuditPath -MaximumRolloverFiles 15 -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct MaximumRolloverFiles parameters' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['MaximumRolloverFiles'] | Should -Be 15 + $parameters.ContainsKey('MaximumFiles') | Should -BeFalse + $parameters.ContainsKey('ReserveDiskSpace') | Should -BeFalse + } + + It 'Should recreate the audit with the same MaximumRolloverFiles properties' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.MaximumRolloverFiles | Should -Be 15 + } + } + + Context 'When converting an audit with OnFailure setting' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_OnFailure_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -LogType 'ApplicationLog' -OnFailure 'FailOperation' -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct OnFailure parameter' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['OnFailure'] | Should -Be 'FailOperation' + } + + It 'Should recreate the audit with the same OnFailure setting' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.OnFailure | Should -Be 'FailOperation' + } + } + + Context 'When converting an audit with QueueDelay setting' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_QueueDelay_' + (Get-Random) + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -LogType 'ApplicationLog' -QueueDelay 3000 -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct QueueDelay parameter' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['QueueDelay'] | Should -Be 3000 + } + + It 'Should recreate the audit with the same QueueDelay setting' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.QueueDelay | Should -Be 3000 + } + } + + Context 'When converting an audit with AuditGuid' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_Guid_' + (Get-Random) + $script:testGuid = [System.Guid]::NewGuid().ToString() + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -LogType 'ApplicationLog' -AuditGuid $script:testGuid -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct AuditGuid parameter' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['AuditGuid'] | Should -Be $script:testGuid + } + + It 'Should allow overriding AuditGuid with new value' { + $newGuid = [System.Guid]::NewGuid().ToString() + + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit; NewGuid = $newGuid } -ScriptBlock { + param($AuditObject, $NewGuid) + + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject -AuditGuid $NewGuid + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['AuditGuid'] | Should -Be $newGuid + $parameters['AuditGuid'] | Should -Not -Be $script:testAudit.Guid + } + + It 'Should recreate the audit with the same AuditGuid' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.Guid | Should -Be $script:testGuid + } + } + + Context 'When converting an audit with AuditFilter' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_Filter_' + (Get-Random) + $script:testFilter = "([database_name] = 'master')" + $script:expectedFilter = "([database_name]='master')" # SQL Server normalizes the filter + $script:testAudit = New-SqlDscAudit -ServerObject $script:serverObject -Name $script:testAuditName -LogType 'ApplicationLog' -AuditFilter $script:testFilter -PassThru -Force -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return correct AuditFilter parameter' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['AuditFilter'] | Should -Be $script:expectedFilter + } + + It 'Should recreate the audit with the same AuditFilter' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.Filter | Should -Be $script:expectedFilter + } + } + + Context 'When converting a complex audit with all properties' { + BeforeAll { + $script:testAuditName = 'SqlDscTestConvert_Complex_' + (Get-Random) + $script:testGuid = [System.Guid]::NewGuid().ToString() + $script:testFilter = "([database_name] = 'tempdb')" + $script:expectedFilter = "([database_name]='tempdb')" + $script:testAudit = New-SqlDscAudit ` + -ServerObject $script:serverObject ` + -Name $script:testAuditName ` + -Path $script:testAuditPath ` + -MaximumFileSize 200 ` + -MaximumFileSizeUnit 'Gigabyte' ` + -MaximumFiles 20 ` + -ReserveDiskSpace ` + -OnFailure 'Shutdown' ` + -QueueDelay 2000 ` + -AuditGuid $script:testGuid ` + -AuditFilter $script:testFilter ` + -PassThru ` + -Force ` + -ErrorAction Stop + } + + AfterAll { + if ($script:testAudit) + { + Remove-SqlDscAudit -AuditObject $script:testAudit -Force -ErrorAction 'SilentlyContinue' + } + } + + It 'Should return all correct parameters for complex audit' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + $parameters | Should -Not -BeNullOrEmpty + $parameters['Path'].TrimEnd('\', '/') | Should -Be $script:testAuditPath.TrimEnd('\', '/') + $parameters['MaximumFileSize'] | Should -Be 200 + $parameters['MaximumFileSizeUnit'] | Should -Be 'Gigabyte' + $parameters['MaximumFiles'] | Should -Be 20 + $parameters['ReserveDiskSpace'] | Should -BeTrue + $parameters['OnFailure'] | Should -Be 'Shutdown' + $parameters['QueueDelay'] | Should -Be 2000 + $parameters['AuditGuid'] | Should -Be $script:testGuid + $parameters['AuditFilter'] | Should -Be $script:expectedFilter + } + + It 'Should recreate the complex audit with all the same properties' { + $parameters = InModuleScope -Parameters @{ AuditObject = $script:testAudit } -ScriptBlock { + ConvertTo-AuditNewParameterSet -AuditObject $AuditObject + } + + # Remove the original audit + Remove-SqlDscAudit -AuditObject $script:testAudit -Confirm:$false + + # Recreate using the parameters + $recreatedAudit = New-SqlDscAudit @parameters -Confirm:$false -PassThru + + $recreatedAudit | Should -Not -BeNullOrEmpty + $recreatedAudit.Name | Should -Be $script:testAudit.Name + $recreatedAudit.FilePath.TrimEnd('\', '/') | Should -Be $script:testAuditPath.TrimEnd('\', '/') + $recreatedAudit.MaximumFileSize | Should -Be 200 + $recreatedAudit.MaximumFileSizeUnit | Should -Be 'GB' + $recreatedAudit.MaximumFiles | Should -Be 20 + $recreatedAudit.ReserveDiskSpace | Should -BeTrue + $recreatedAudit.OnFailure | Should -Be 'Shutdown' + $recreatedAudit.QueueDelay | Should -Be 2000 + $recreatedAudit.Guid | Should -Be $script:testGuid + $recreatedAudit.Filter | Should -Be $script:expectedFilter + } + } +} diff --git a/tests/Unit/Private/ConvertTo-AuditNewParameterSet.Tests.ps1 b/tests/Unit/Private/ConvertTo-AuditNewParameterSet.Tests.ps1 new file mode 100644 index 0000000000..0998858a1e --- /dev/null +++ b/tests/Unit/Private/ConvertTo-AuditNewParameterSet.Tests.ps1 @@ -0,0 +1,246 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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 have 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 noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +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:moduleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'ConvertTo-AuditNewParameterSet' -Tag 'Private' { + Context 'When converting a File audit with basic properties' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockServerObject.InstanceName = 'MSSQLSERVER' + + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.OnFailure = 'Continue' + $script:mockAuditObject.QueueDelay = 1000 + } + } + + It 'Should return correct parameters for basic file audit' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result | Should -BeOfType [System.Collections.Hashtable] + $result['ServerObject'] | Should -Be $script:mockServerObject + $result['Name'] | Should -Be 'TestAudit' + $result['Path'] | Should -Be 'C:\Temp' + $result['OnFailure'] | Should -Be 'Continue' + $result['QueueDelay'] | Should -Be 1000 + } + } + } + + Context 'When converting a File audit with file size limits' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.MaximumFileSize = 100 + $script:mockAuditObject.MaximumFileSizeUnit = 'MB' + } + } + + It 'Should return correct parameters including file size' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['MaximumFileSize'] | Should -Be 100 + $result['MaximumFileSizeUnit'] | Should -Be 'Megabyte' + } + } + } + + Context 'When converting a File audit with MaximumFiles' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.MaximumFiles = 10 + $script:mockAuditObject.ReserveDiskSpace = $true + } + } + + It 'Should return correct parameters including MaximumFiles and ReserveDiskSpace' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['MaximumFiles'] | Should -Be 10 + $result['ReserveDiskSpace'] | Should -BeTrue + $result.ContainsKey('MaximumRolloverFiles') | Should -BeFalse + } + } + } + + Context 'When converting a File audit with MaximumRolloverFiles' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.MaximumRolloverFiles = 5 + } + } + + It 'Should return correct parameters including MaximumRolloverFiles' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['MaximumRolloverFiles'] | Should -Be 5 + $result.ContainsKey('MaximumFiles') | Should -BeFalse + $result.ContainsKey('ReserveDiskSpace') | Should -BeFalse + } + } + } + + Context 'When converting an ApplicationLog audit' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'ApplicationLog' + } + } + + It 'Should return correct parameters for ApplicationLog' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['LogType'] | Should -Be 'ApplicationLog' + $result.ContainsKey('Path') | Should -BeFalse + } + } + } + + Context 'When converting a SecurityLog audit' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'SecurityLog' + } + } + + It 'Should return correct parameters for SecurityLog' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['LogType'] | Should -Be 'SecurityLog' + $result.ContainsKey('Path') | Should -BeFalse + } + } + } + + Context 'When converting an audit with AuditFilter' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.Filter = 'database_name = ''master''' + } + } + + It 'Should return correct parameters including AuditFilter' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['AuditFilter'] | Should -Be 'database_name = ''master''' + } + } + } + + Context 'When providing a new AuditGuid parameter' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.Guid = '12345678-1234-1234-1234-123456789012' + } + } + + It 'Should use the provided GUID instead of the existing one' { + InModuleScope -ScriptBlock { + $newGuid = '87654321-4321-4321-4321-210987654321' + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject -AuditGuid $newGuid + + $result['AuditGuid'] | Should -Be $newGuid + } + } + } + + Context 'When audit has existing GUID' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $script:mockAuditObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @($script:mockServerObject, 'TestAudit') + $script:mockAuditObject.DestinationType = 'File' + $script:mockAuditObject.FilePath = 'C:\Temp' + $script:mockAuditObject.Guid = '12345678-1234-1234-1234-123456789012' + } + } + + It 'Should include the existing GUID' { + InModuleScope -ScriptBlock { + $result = ConvertTo-AuditNewParameterSet -AuditObject $script:mockAuditObject + + $result['AuditGuid'] | Should -Be '12345678-1234-1234-1234-123456789012' + } + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 b/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 index 014da6d77d..658dc1fe8b 100644 --- a/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscAudit.Tests.ps1 @@ -52,52 +52,52 @@ AfterAll { 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 = 'ServerObject' + MockExpectedParameters = '-ServerObject -Name [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'ServerObjectWithSize' - MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'ServerObjectWithSize' + MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'ServerObjectWithMaxFiles' - MockExpectedParameters = '-ServerObject -Name -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'ServerObjectWithMaxFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'ServerObjectWithMaxRolloverFiles' - MockExpectedParameters = '-ServerObject -Name -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'ServerObjectWithMaxRolloverFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-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 = 'ServerObjectWithSizeAndMaxFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-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 = 'ServerObjectWithSizeAndMaxRolloverFiles' + MockExpectedParameters = '-ServerObject -Name -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'AuditObject' - MockExpectedParameters = '-AuditObject [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'AuditObject' + MockExpectedParameters = '-AuditObject [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'AuditObjectWithSize' - MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'AuditObjectWithSize' + MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'AuditObjectWithMaxFiles' - MockExpectedParameters = '-AuditObject -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'AuditObjectWithMaxFiles' + MockExpectedParameters = '-AuditObject -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'AuditObjectWithMaxRolloverFiles' - MockExpectedParameters = '-AuditObject -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'AuditObjectWithMaxRolloverFiles' + MockExpectedParameters = '-AuditObject -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-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 = 'AuditObjectWithSizeAndMaxFiles' + MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit -MaximumFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-ReserveDiskSpace] [-WhatIf] [-Confirm] []' } @{ - MockParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles' - MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' + MockParameterSetName = 'AuditObjectWithSizeAndMaxRolloverFiles' + MockExpectedParameters = '-AuditObject -MaximumFileSize -MaximumFileSizeUnit -MaximumRolloverFiles [-AuditFilter ] [-OnFailure ] [-QueueDelay ] [-AuditGuid ] [-AllowAuditGuidChange] [-Force] [-Refresh] [-PassThru] [-Path ] [-WhatIf] [-Confirm] []' } ) { $result = (Get-Command -Name 'Set-SqlDscAudit').ParameterSets | @@ -106,11 +106,11 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { } | Select-Object -Property @( @{ - Name = 'ParameterSetName' + Name = 'ParameterSetName' Expression = { $_.Name } }, @{ - Name = 'ParameterListAsString' + Name = 'ParameterListAsString' Expression = { $_.ToString() } } ) @@ -144,7 +144,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ ServerObject = $mockServerObject - Name = 'Log1' + Name = 'Log1' } } @@ -282,8 +282,8 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { It 'Should throw the correct error' { $mockNewSqlDscAuditParameters = @{ ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' - Path = Get-TemporaryFolder - Name = 'Log1' + Path = Get-TemporaryFolder + Name = 'Log1' } $mockErrorMessage = InModuleScope -ScriptBlock { @@ -299,8 +299,8 @@ 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' + ServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + Name = 'Log1' MaximumFileSize = $_ } @@ -317,8 +317,8 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { 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 = $_ + Name = 'Log1' + QueueDelay = $_ } $mockErrorMessage = 'Cannot validate argument on parameter ''QueueDelay''. ' @@ -347,7 +347,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -383,7 +383,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -420,7 +420,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -456,7 +456,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -506,7 +506,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -540,9 +540,44 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $script:mockMethodAlterCallCount += 1 } -PassThru -Force + # Set a different initial GUID + $script:mockAuditObject.Guid = 'a1111111-1111-1111-1111-111111111111' + + Mock -CommandName ConvertTo-AuditNewParameterSet -MockWith { + return @{ + ServerObject = $AuditObject.Parent + Name = $AuditObject.Name + LogType = 'ApplicationLog' + AuditGuid = $AuditGuid + } + } + + Mock -CommandName Remove-SqlDscAudit + + Mock -CommandName New-SqlDscAudit -MockWith { + $newAudit = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $ServerObject, + $Name + ) + + # Add the Alter method + $newAudit | Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -Force + + # Set the Guid property directly on the SMO object (convert string to GUID) + # PowerShell should automatically convert the string to a GUID when assigning + if ($null -ne $AuditGuid -and $AuditGuid -ne '') + { + $newAudit.Guid = $AuditGuid + } + + return $newAudit + } + $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -550,14 +585,78 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $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 + It 'Should recreate the audit with the new GUID when AllowAuditGuidChange is specified' { + $result = Set-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' -AllowAuditGuidChange -PassThru @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' + # Debug: Check what we got back + $result | Should -Not -BeNullOrEmpty -Because 'PassThru should return the audit object' + $result.Name | Should -Be 'Log1' -Because 'The audit name should match' + $result.Guid | Should -Be 'b5962b93-a359-42ef-bf1e-193e8a5f6222' -Because 'The GUID should be set to the new value' - $mockMethodAlterCallCount | Should -Be 1 + # Verify the helper function was called with correct GUID + Should -Invoke -CommandName ConvertTo-AuditNewParameterSet -Exactly -Times 1 -Scope It -ParameterFilter { + $AuditGuid -eq 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + } + + # Verify the audit was removed + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 1 -Scope It + + # Verify the audit was recreated with PassThru and the correct GUID + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 1 -Scope It -ParameterFilter { + $PassThru.IsPresent -and $AuditGuid -eq 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + } + } + + Context 'When AuditGuid is same as existing GUID' { + It 'Should not recreate the audit but still call Alter' { + $mockAuditObject.Guid = 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + + Set-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' -AllowAuditGuidChange @mockDefaultParameters + + # Should not invoke helper functions when GUID is not changing + Should -Invoke -CommandName ConvertTo-AuditNewParameterSet -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 0 -Scope It + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 0 -Scope It + + # Alter() is still called even when no property values change + $mockMethodAlterCallCount | Should -Be 1 -Because 'Alter() is always called in the normal update path' + } + } + + Context 'When AuditGuid is same as existing GUID but other properties change' { + It 'Should update properties without recreating the audit' { + # Set the existing GUID and QueueDelay + $mockAuditObject.Guid = 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + $mockAuditObject.QueueDelay = 500 + + # Call with same GUID but different QueueDelay + Set-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' -QueueDelay 1000 -AllowAuditGuidChange @mockDefaultParameters + + # Should not invoke helper functions when GUID is not changing + Should -Invoke -CommandName ConvertTo-AuditNewParameterSet -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 0 -Scope It + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 0 -Scope It + + # Verify the property was updated + $mockAuditObject.QueueDelay | Should -Be 1000 -Because 'QueueDelay should be updated to the new value' + + # Alter() should be called to persist the property change + $mockMethodAlterCallCount | Should -Be 1 -Because 'Alter() is called to update the property' + } + } + + Context 'When trying to change AuditGuid without AllowAuditGuidChange parameter' { + It 'Should throw the correct error' { + # Ensure the GUID is different from what we're trying to set + $mockAuditObject.Guid = 'a1111111-1111-1111-1111-111111111111' + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.Audit_AuditGuidChangeRequiresAllowParameter -f 'Log1' + } + + { Set-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' @mockDefaultParameters } | + Should -Throw -ExpectedMessage $mockErrorMessage + } } Context 'When passing an invalid GUID' { @@ -590,7 +689,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -638,7 +737,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -674,7 +773,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -710,7 +809,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -748,7 +847,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -768,6 +867,94 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { } } + Context 'When changing AuditGuid with AllowAuditGuidChange and other properties' { + BeforeAll { + $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 + + # Set a different initial GUID + $script:mockAuditObject.Guid = 'a1111111-1111-1111-1111-111111111111' + + Mock -CommandName ConvertTo-AuditNewParameterSet -MockWith { + return @{ + ServerObject = $AuditObject.Parent + Name = $AuditObject.Name + LogType = 'ApplicationLog' + AuditGuid = $AuditGuid + } + } + + Mock -CommandName Remove-SqlDscAudit + + # Track recursive call to Set-SqlDscAudit + $script:setAuditRecursiveCallCount = 0 + + Mock -CommandName New-SqlDscAudit -MockWith { + $newAudit = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Audit' -ArgumentList @( + $ServerObject, + $Name + ) + + # Add the Alter method + $newAudit | Add-Member -MemberType 'ScriptMethod' -Name 'Alter' -Value { + $script:mockMethodAlterCallCount += 1 + } -Force + + # Set the Guid property directly on the SMO object (convert string to GUID) + # PowerShell should automatically convert the string to a GUID when assigning + if ($null -ne $AuditGuid -and $AuditGuid -ne '') + { + $newAudit.Guid = $AuditGuid + } + + return $newAudit + } + + $mockDefaultParameters = @{ + AuditObject = $mockAuditObject + Force = $true + } + } + + BeforeEach { + $script:mockMethodAlterCallCount = 0 + $script:setAuditRecursiveCallCount = 0 + } + + It 'Should recreate the audit with new GUID and apply other property changes' { + $result = Set-SqlDscAudit -AuditGuid 'b5962b93-a359-42ef-bf1e-193e8a5f6222' -AllowAuditGuidChange -QueueDelay 1000 -PassThru @mockDefaultParameters + + # Verify the helper function was called with correct GUID + Should -Invoke -CommandName ConvertTo-AuditNewParameterSet -Exactly -Times 1 -Scope It -ParameterFilter { + $AuditGuid -eq 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + } + + # Verify the audit was removed + Should -Invoke -CommandName Remove-SqlDscAudit -Exactly -Times 1 -Scope It + + # Verify the audit was recreated with PassThru and the correct GUID + Should -Invoke -CommandName New-SqlDscAudit -Exactly -Times 1 -Scope It -ParameterFilter { + $PassThru.IsPresent -and $AuditGuid -eq 'b5962b93-a359-42ef-bf1e-193e8a5f6222' + } + + # The result should not be null + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'Log1' + + # The QueueDelay should be set (via recursive call to Set-SqlDscAudit) + $result.QueueDelay | Should -Be 1000 + $result.Guid | Should -Be 'b5962b93-a359-42ef-bf1e-193e8a5f6222' -Because 'The GUID should be set to the new value' + } + } + Context 'When switching from MaximumRolloverFiles to MaximumFiles' { BeforeAll { $script:mockAuditObject = $null @@ -787,7 +974,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } } @@ -828,7 +1015,7 @@ Describe 'Set-SqlDscAudit' -Tag 'Public' { $mockDefaultParameters = @{ AuditObject = $mockAuditObject - Force = $true + Force = $true } }