diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa08707a..5a2dfdc9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- SqlAGDatabase + - Fix for issue ([issue #1492](https://github.com/dsccommunity/SqlServerDsc/issues/1492)) + added AutomaticSeeding for this resource. In Set-TargetResource added logic that looks + at all replicas of an availability group. When automatic seedig is found, it will use that. + - Lots of extra tests to check AutomaticSeeding. + - The parameter `BackupPath` is still needed just in case a database never has been backuped before. - SqlMaxDop - Fixes ([issue #396](https://github.com/dsccommunity/SqlServerDsc/issues/396)). Added three return values in Get-Target resource. diff --git a/source/DSCResources/DSC_SqlAGDatabase/DSC_SqlAGDatabase.psm1 b/source/DSCResources/DSC_SqlAGDatabase/DSC_SqlAGDatabase.psm1 index d99092228..526b87748 100644 --- a/source/DSCResources/DSC_SqlAGDatabase/DSC_SqlAGDatabase.psm1 +++ b/source/DSCResources/DSC_SqlAGDatabase/DSC_SqlAGDatabase.psm1 @@ -234,7 +234,7 @@ function Set-TargetResource # Create a hash table to store the databases that failed to be added to the Availability Group $databasesToAddFailures = @{} - # Create a hash table to store the databases that failed to be added to the Availability Group + # Create a hash table to store the databases that failed to be removed from the Availability Group $databasesToRemoveFailures = @{} if ( $databasesToAddToAvailabilityGroup.Count -gt 0 ) @@ -422,57 +422,89 @@ function Set-TargetResource } } - if ( $prerequisiteCheckFailures.Count -eq 0 ) - { - $databaseFullBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Full_$(Get-Date -Format 'yyyyMMddhhmmss').bak" - $databaseLogBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Log_$(Get-Date -Format 'yyyyMMddhhmmss').trn" - - # Build the backup parameters. If no backup was previously taken, a standard full will be taken. Otherwise a CopyOnly backup will be taken. - $backupSqlDatabaseParameters = @{ - DatabaseObject = $databaseObject - BackupAction = 'Database' - BackupFile = $databaseFullBackupFile - ErrorAction = 'Stop' - } + <# + Determine whether SEEDING_MODE = for all replicas is Automatic or Manual. If all replicas are Automatic, a restore is not needed. + When the database is fresh, and never backuped before, a backup is still needed. + #> + $backupNeeded = $false + $restoreNeeded = $false - # If database object last backup data not equal to 0 then backup with CopyOnly. - if ( $databaseObject.LastBackupDate -ne 0 ) + foreach ( $availabilityGroupReplica in $secondaryReplicas ) + { + if ( $availabilityGroupReplica.SeedingMode -eq 'Manual') { - $backupSqlDatabaseParameters.Add('CopyOnly', $true) + $backupNeeded = $true + $restoreNeeded = $true } + } - try + if ( $backupNeeded -eq $false) + { + <# + Because an availability group can only be made when the database has at least one backup, + if there is no backup, create one. This backup is not needed for restore, only the initial setup + of the LSN in the database is needed + #> + if ( $primaryServerObject.Databases[$databaseToAddToAvailabilityGroup].CreateDate -gt $primaryServerObject.Databases[$databaseToAddToAvailabilityGroup].LastBackupDate) { - Backup-SqlDatabase @backupSqlDatabaseParameters + $needsBackup = $true } - catch + } + + if ( $prerequisiteCheckFailures.Count -eq 0 ) + { + if ( $backupNeeded) { - # Log the failure - $databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception) + $databaseFullBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Full_$(Get-Date -Format 'yyyyMMddhhmmss').bak" + $databaseLogBackupFile = Join-Path -Path $BackupPath -ChildPath "$($databaseObject.Name)_Log_$(Get-Date -Format 'yyyyMMddhhmmss').trn" + + # Build the backup parameters. If no backup was previously taken, a standard full will be taken. Otherwise a CopyOnly backup will be taken. + $backupSqlDatabaseParameters = @{ + DatabaseObject = $databaseObject + BackupAction = 'Database' + BackupFile = $databaseFullBackupFile + ErrorAction = 'Stop' + } - # Move on to the next database - continue - } + # If database object last backup data not equal to 0 then backup with CopyOnly. + if ( $databaseObject.LastBackupDate -ne 0 ) + { + $backupSqlDatabaseParameters.Add('CopyOnly', $true) + } - # Create the parameters to perform a transaction log backup - $backupSqlDatabaseLogParams = @{ - DatabaseObject = $databaseObject - BackupAction = 'Log' - BackupFile = $databaseLogBackupFile - ErrorAction = 'Stop' - } + try + { + Backup-SqlDatabase @backupSqlDatabaseParameters + } + catch + { + # Log the failure + $databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception) - try - { - Backup-SqlDatabase @backupSqlDatabaseLogParams - } - catch - { - # Log the failure - $databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception) + # Move on to the next database + continue + } - # Move on to the next database - continue + # Create the parameters to perform a transaction log backup + $backupSqlDatabaseLogParams = @{ + DatabaseObject = $databaseObject + BackupAction = 'Log' + BackupFile = $databaseLogBackupFile + ErrorAction = 'Stop' + } + + try + { + Backup-SqlDatabase @backupSqlDatabaseLogParams + } + catch + { + # Log the failure + $databasesToAddFailures.Add($databaseToAddToAvailabilityGroup, $_.Exception) + + # Move on to the next database + continue + } } # Add the database to the availability group on the primary instance @@ -489,62 +521,65 @@ function Set-TargetResource continue } - # Need to restore the database with a query in order to impersonate the correct login - $restoreDatabaseQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder - - if ( $MatchDatabaseOwner ) + if ( $restoreNeeded) { - $restoreDatabaseQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null - $restoreDatabaseQueryStringBuilder.Append($databaseObject.Owner) | Out-Null + # Need to restore the database with a query in order to impersonate the correct login + $restoreDatabaseQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder + + if ( $MatchDatabaseOwner ) + { + $restoreDatabaseQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null + $restoreDatabaseQueryStringBuilder.Append($databaseObject.Owner) | Out-Null + $restoreDatabaseQueryStringBuilder.AppendLine('''') | Out-Null + } + + $restoreDatabaseQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null + $restoreDatabaseQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null + $restoreDatabaseQueryStringBuilder.AppendLine(']') | Out-Null + $restoreDatabaseQueryStringBuilder.Append('FROM DISK = ''') | Out-Null + $restoreDatabaseQueryStringBuilder.Append($databaseFullBackupFile) | Out-Null $restoreDatabaseQueryStringBuilder.AppendLine('''') | Out-Null - } + $restoreDatabaseQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null - $restoreDatabaseQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null - $restoreDatabaseQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null - $restoreDatabaseQueryStringBuilder.AppendLine(']') | Out-Null - $restoreDatabaseQueryStringBuilder.Append('FROM DISK = ''') | Out-Null - $restoreDatabaseQueryStringBuilder.Append($databaseFullBackupFile) | Out-Null - $restoreDatabaseQueryStringBuilder.AppendLine('''') | Out-Null - $restoreDatabaseQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null + if ( $ReplaceExisting ) + { + $restoreDatabaseQueryStringBuilder.Append(',REPLACE') | Out-Null + } - if ( $ReplaceExisting ) - { - $restoreDatabaseQueryStringBuilder.Append(',REPLACE') | Out-Null - } + if ( $MatchDatabaseOwner ) + { + $restoreDatabaseQueryStringBuilder.AppendLine() | Out-Null + $restoreDatabaseQueryStringBuilder.Append('REVERT') | Out-Null + } + $restoreDatabaseQueryString = $restoreDatabaseQueryStringBuilder.ToString() - if ( $MatchDatabaseOwner ) - { - $restoreDatabaseQueryStringBuilder.AppendLine() | Out-Null - $restoreDatabaseQueryStringBuilder.Append('REVERT') | Out-Null - } - $restoreDatabaseQueryString = $restoreDatabaseQueryStringBuilder.ToString() + # Need to restore the database with a query in order to impersonate the correct login + $restoreLogQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder - # Need to restore the database with a query in order to impersonate the correct login - $restoreLogQueryStringBuilder = New-Object -TypeName System.Text.StringBuilder + if ( $MatchDatabaseOwner ) + { + $restoreLogQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null + $restoreLogQueryStringBuilder.Append($databaseObject.Owner) | Out-Null + $restoreLogQueryStringBuilder.AppendLine('''') | Out-Null + } - if ( $MatchDatabaseOwner ) - { - $restoreLogQueryStringBuilder.Append('EXECUTE AS LOGIN = ''') | Out-Null - $restoreLogQueryStringBuilder.Append($databaseObject.Owner) | Out-Null + $restoreLogQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null + $restoreLogQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null + $restoreLogQueryStringBuilder.AppendLine(']') | Out-Null + $restoreLogQueryStringBuilder.Append('FROM DISK = ''') | Out-Null + $restoreLogQueryStringBuilder.Append($databaseLogBackupFile) | Out-Null $restoreLogQueryStringBuilder.AppendLine('''') | Out-Null - } + $restoreLogQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null - $restoreLogQueryStringBuilder.Append('RESTORE DATABASE [') | Out-Null - $restoreLogQueryStringBuilder.Append($databaseToAddToAvailabilityGroup) | Out-Null - $restoreLogQueryStringBuilder.AppendLine(']') | Out-Null - $restoreLogQueryStringBuilder.Append('FROM DISK = ''') | Out-Null - $restoreLogQueryStringBuilder.Append($databaseLogBackupFile) | Out-Null - $restoreLogQueryStringBuilder.AppendLine('''') | Out-Null - $restoreLogQueryStringBuilder.Append('WITH NORECOVERY') | Out-Null + if ( $MatchDatabaseOwner ) + { + $restoreLogQueryStringBuilder.AppendLine() | Out-Null + $restoreLogQueryStringBuilder.Append('REVERT') | Out-Null + } - if ( $MatchDatabaseOwner ) - { - $restoreLogQueryStringBuilder.AppendLine() | Out-Null - $restoreLogQueryStringBuilder.Append('REVERT') | Out-Null + $restoreLogQueryString = $restoreLogQueryStringBuilder.ToString() } - $restoreLogQueryString = $restoreLogQueryStringBuilder.ToString() - try { foreach ( $availabilityGroupReplica in $secondaryReplicas ) @@ -554,9 +589,12 @@ function Set-TargetResource $currentAvailabilityGroupReplicaServerObject = Connect-SQL @connectSqlParameters $currentReplicaAvailabilityGroupObject = $currentAvailabilityGroupReplicaServerObject.AvailabilityGroups[$AvailabilityGroupName] - # Restore the database - Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreDatabaseQueryString -StatementTimeout 0 - Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreLogQueryString -StatementTimeout 0 + if ( $availabilityGroupReplica.SeedingMode -eq 'MANUAL') + { + # Restore the database + Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreDatabaseQueryString -StatementTimeout 0 + Invoke-Query -ServerName $currentAvailabilityGroupReplicaServerObject.NetName -InstanceName $currentAvailabilityGroupReplicaServerObject.ServiceName -Database master -Query $restoreLogQueryString -StatementTimeout 0 + } # Add the database to the Availability Group Add-SqlAvailabilityDatabase -InputObject $currentReplicaAvailabilityGroupObject -Database $databaseToAddToAvailabilityGroup @@ -572,8 +610,12 @@ function Set-TargetResource } finally { - # Clean up the backup files - Remove-Item -Path $databaseFullBackupFile, $databaseLogBackupFile -Force -ErrorAction Continue + # When a backup was made, it should be removed. + if ( $backupNeeded) + { + # Clean up the backup files + Remove-Item -Path $databaseFullBackupFile, $databaseLogBackupFile -Force -ErrorAction Continue + } } } else diff --git a/source/DSCResources/DSC_SqlAGDatabase/README.md b/source/DSCResources/DSC_SqlAGDatabase/README.md index 05d446b59..e921926f9 100644 --- a/source/DSCResources/DSC_SqlAGDatabase/README.md +++ b/source/DSCResources/DSC_SqlAGDatabase/README.md @@ -2,6 +2,8 @@ The `SqlAGDatabase` DSC resource is used to add databases or remove databases from a specified availability group. +When a replica has Automatic seeding on Automatic, no restore is use for that replica. +When all replicas are on automatic seeding, no backup is made, unless the database has never been backuped. ## Requirements diff --git a/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 b/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 index d3139cb25..a2953a0ad 100644 --- a/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 +++ b/tests/Unit/DSC_SqlAGDatabase.Tests.ps1 @@ -48,335 +48,335 @@ Invoke-TestSetup try { InModuleScope $script:dscResourceName { + Describe 'SqlAGDatabase\Get-TargetResource' { + BeforeAll { + #region Parameter Mocks - #region Parameter Mocks - - # The databases defined in the resource - $mockDatabaseNameParameter = @( - 'DB*' - 'AnotherDB' - '3rd*OfDatabase' - '4th*OfDatabase' - ) + # The databases defined in the resource + $mockDatabaseNameParameter = @( + 'DB*' + 'AnotherDB' + '3rd*OfDatabase' + '4th*OfDatabase' + ) - $mockDatabaseNameParameterWithNonExistingDatabases = @( - 'NotFound*' - 'Unknown1' - ) + $mockDatabaseNameParameterWithNonExistingDatabases = @( + 'NotFound*' + 'Unknown1' + ) - $mockBackupPath = 'X:\Backup' + $mockBackupPath = 'X:\Backup' - $mockProcessOnlyOnActiveNode = $false + $mockProcessOnlyOnActiveNode = $false - #endregion Parameter Mocks + #endregion Parameter Mocks - #region mock names + #region mock names - $mockServerObjectDomainInstanceName = 'Server1' - $mockPrimaryServerObjectDomainInstanceName = 'Server2' - $mockAvailabilityGroupObjectName = 'AvailabilityGroup1' - $mockAvailabilityGroupWithoutDatabasesObjectName = 'AvailabilityGroupWithoutDatabases' - $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName = 'AvailabilityGroup2' - $mockTrueLogin = 'Login1' - $mockDatabaseOwner = 'DatabaseOwner1' + $mockServerObjectDomainInstanceName = 'Server1' + $mockPrimaryServerObjectDomainInstanceName = 'Server2' + $mockAvailabilityGroupObjectName = 'AvailabilityGroup1' + $mockAvailabilityGroupWithoutDatabasesObjectName = 'AvailabilityGroupWithoutDatabases' + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName = 'AvailabilityGroup2' + $mockTrueLogin = 'Login1' + $mockDatabaseOwner = 'DatabaseOwner1' + $mockReplicaSeedingMode = 'Manual' + #endregion mock names - #endregion mock names + #region Availability Replica Mocks - #region Availability Replica Mocks + $mockAvailabilityReplicaObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplicaCollection + foreach ( $mockAvailabilityReplicaName in @('Server1','Server2') ) + { + $newAvailabilityReplicaObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $newAvailabilityReplicaObject.Name = $mockAvailabilityReplicaName + $newAvailabilityReplicaObject.SeedingMode = 'Manual' - $mockAvailabilityReplicaObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplicaCollection - foreach ( $mockAvailabilityReplicaName in @('Server1','Server2') ) - { - $newAvailabilityReplicaObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica - $newAvailabilityReplicaObject.Name = $mockAvailabilityReplicaName + if ( $mockServerObjectDomainInstanceName -eq $mockAvailabilityReplicaName ) + { + $newAvailabilityReplicaObject.Role = 'Primary' + } - if ( $mockServerObjectDomainInstanceName -eq $mockAvailabilityReplicaName ) - { - $newAvailabilityReplicaObject.Role = 'Primary' - } + $mockAvailabilityReplicaObjects.Add($newAvailabilityReplicaObject) + } - $mockAvailabilityReplicaObjects.Add($newAvailabilityReplicaObject) - } + #endregion Availability Replica Mocks - #endregion Availability Replica Mocks + #region Availability Group Mocks - #region Availability Group Mocks + $mockAvailabilityDatabaseNames = @( + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) - $mockAvailabilityDatabaseNames = @( - 'DB2' - '3rdTypeOfDatabase' - 'UndefinedDatabase' - ) + $mockAvailabilityDatabaseAbsentResults = @( + 'DB2' + '3rdTypeOfDatabase' + ) - $mockAvailabilityDatabaseAbsentResults = @( - 'DB2' - '3rdTypeOfDatabase' - ) + $mockAvailabilityDatabaseExactlyAddResults = @( + 'DB1' + ) - $mockAvailabilityDatabaseExactlyAddResults = @( - 'DB1' - ) + $mockAvailabilityDatabaseExactlyRemoveResults = @( + 'UndefinedDatabase' + ) - $mockAvailabilityDatabaseExactlyRemoveResults = @( - 'UndefinedDatabase' - ) + $mockAvailabilityDatabasePresentResults = @( + 'DB1' + ) - $mockAvailabilityDatabasePresentResults = @( - 'DB1' - ) + $mockAvailabilityDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseCollection + foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) + { + $newAvailabilityDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabase + $newAvailabilityDatabaseObject.Name = $mockAvailabilityDatabaseName + $mockAvailabilityDatabaseObjects.Add($newAvailabilityDatabaseObject) + } - $mockAvailabilityDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseCollection - foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) - { - $newAvailabilityDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabase - $newAvailabilityDatabaseObject.Name = $mockAvailabilityDatabaseName - $mockAvailabilityDatabaseObjects.Add($newAvailabilityDatabaseObject) - } + $mockBadAvailabilityGroupObject = New-Object -TypeName Object + + $mockAvailabilityGroupObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObject.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObject.Name = $mockAvailabilityGroupObjectName + $mockAvailabilityGroupObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupWithoutDatabasesObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupWithoutDatabasesObject.Name = $mockAvailabilityGroupWithoutDatabasesObjectName + $mockAvailabilityGroupWithoutDatabasesObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupWithoutDatabasesObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.PrimaryReplicaServerName = $mockPrimaryServerObjectDomainInstanceName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + #endregion Availability Group Mocks + + #region Certificate Mocks + + [byte[]]$mockThumbprint1 = @( + 83 + 121 + 115 + 116 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 46 + 85 + 84 + 70 + 56 + 69 + 110 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + [byte[]]$mockThumbprint2 = @( + 83 + 121 + 115 + 23 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 85 + 85 + 84 + 70 + 56 + 69 + 23 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + $mockCertificateObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject1.Thumbprint = $mockThumbprint1 + + $mockCertificateObject2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject2.Thumbprint = $mockThumbprint2 + + $mockDatabaseEncryptionKeyObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseEncryptionKey + $mockDatabaseEncryptionKeyObject.EncryptorName = 'TDE Cert' + $mockDatabaseEncryptionKeyObject.Thumbprint = $mockThumbprint1 + + #endregion Certificate Mocks + + #region Database File Mocks + + $mockDataFilePath = 'E:\SqlData' + $mockLogFilePath = 'F:\SqlLog' + $mockDataFilePathIncorrect = 'G:\SqlData' + $mockLogFilePathIncorrect = 'H:\SqlData' + + #endregion Database File Mocks + + #region Database Mocks + + # The databases found on the instance + $mockPresentDatabaseNames = @( + 'DB1' + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockMasterDatabaseName = 'master' + $mockMasterDatabaseObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $mockMasterDatabaseObject1.Name = $mockMasterDatabaseName + $mockMasterDatabaseObject1.ID = 1 + $mockMasterDatabaseObject1.Certificates = @($mockCertificateObject1) + $mockMasterDatabaseObject1.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockMasterDatabaseName).mdf" ) ) + } + } + $mockMasterDatabaseObject1.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockMasterDatabaseName).ldf" ) ) + } - $mockBadAvailabilityGroupObject = New-Object -TypeName Object - - $mockAvailabilityGroupObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup - $mockAvailabilityGroupObject.AvailabilityDatabases = $mockAvailabilityDatabaseObjects - $mockAvailabilityGroupObject.Name = $mockAvailabilityGroupObjectName - $mockAvailabilityGroupObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName - $mockAvailabilityGroupObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects - - $mockAvailabilityGroupWithoutDatabasesObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup - $mockAvailabilityGroupWithoutDatabasesObject.Name = $mockAvailabilityGroupWithoutDatabasesObjectName - $mockAvailabilityGroupWithoutDatabasesObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName - $mockAvailabilityGroupWithoutDatabasesObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects - - $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup - $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityDatabases = $mockAvailabilityDatabaseObjects - $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName - $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.PrimaryReplicaServerName = $mockPrimaryServerObjectDomainInstanceName - $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityReplicas = $mockAvailabilityReplicaObjects - - #endregion Availability Group Mocks - - #region Certificate Mocks - - [byte[]]$mockThumbprint1 = @( - 83 - 121 - 115 - 116 - 101 - 109 - 46 - 84 - 101 - 120 - 116 - 46 - 85 - 84 - 70 - 56 - 69 - 110 - 99 - 111 - 100 - 105 - 110 - 103 - ) - - [byte[]]$mockThumbprint2 = @( - 83 - 121 - 115 - 23 - 101 - 109 - 46 - 84 - 101 - 120 - 116 - 85 - 85 - 84 - 70 - 56 - 69 - 23 - 99 - 111 - 100 - 105 - 110 - 103 - ) - - $mockCertificateObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate - $mockCertificateObject1.Thumbprint = $mockThumbprint1 - - $mockCertificateObject2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate - $mockCertificateObject2.Thumbprint = $mockThumbprint2 - - $mockDatabaseEncryptionKeyObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseEncryptionKey - $mockDatabaseEncryptionKeyObject.EncryptorName = 'TDE Cert' - $mockDatabaseEncryptionKeyObject.Thumbprint = $mockThumbprint1 - - #endregion Certificate Mocks - - #region Database File Mocks - - $mockDataFilePath = 'E:\SqlData' - $mockLogFilePath = 'F:\SqlLog' - $mockDataFilePathIncorrect = 'G:\SqlData' - $mockLogFilePathIncorrect = 'H:\SqlData' - - #endregion Database File Mocks - - #region Database Mocks - - # The databases found on the instance - $mockPresentDatabaseNames = @( - 'DB1' - 'DB2' - '3rdTypeOfDatabase' - 'UndefinedDatabase' - ) - - $mockMasterDatabaseName = 'master' - $mockMasterDatabaseObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database - $mockMasterDatabaseObject1.Name = $mockMasterDatabaseName - $mockMasterDatabaseObject1.ID = 1 - $mockMasterDatabaseObject1.Certificates = @($mockCertificateObject1) - $mockMasterDatabaseObject1.FileGroups = @{ - Name = 'PRIMARY' - Files = @{ - FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockMasterDatabaseName).mdf" ) ) - } - } - $mockMasterDatabaseObject1.LogFiles = @{ - FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockMasterDatabaseName).ldf" ) ) - } + $mockDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner - $mockDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection - foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) - { - $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database - $newDatabaseObject.Name = $mockPresentDatabaseName - $newDatabaseObject.FileGroups = @{ - Name = 'PRIMARY' - Files = @{ - FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockPresentDatabaseName).mdf" ) ) + # Add the database object to the database collection + $mockDatabaseObjects.Add($newDatabaseObject) } - } - $newDatabaseObject.LogFiles = @{ - FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockPresentDatabaseName).ldf" ) ) - } - $newDatabaseObject.Owner = $mockDatabaseOwner + $mockDatabaseObjects.Add($mockMasterDatabaseObject1) + + $mockDatabaseObjectsWithIncorrectFileNames = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePathIncorrect, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePathIncorrect, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner - # Add the database object to the database collection - $mockDatabaseObjects.Add($newDatabaseObject) - } - $mockDatabaseObjects.Add($mockMasterDatabaseObject1) - - $mockDatabaseObjectsWithIncorrectFileNames = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection - foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) - { - $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database - $newDatabaseObject.Name = $mockPresentDatabaseName - $newDatabaseObject.FileGroups = @{ - Name = 'PRIMARY' - Files = @{ - FileName = ( [IO.Path]::Combine( $mockDataFilePathIncorrect, "$($mockPresentDatabaseName).mdf" ) ) + # Add the database object to the database collection + $mockDatabaseObjectsWithIncorrectFileNames.Add($newDatabaseObject) } - } - $newDatabaseObject.LogFiles = @{ - FileName = ( [IO.Path]::Combine( $mockLogFilePathIncorrect, "$($mockPresentDatabaseName).ldf" ) ) - } - $newDatabaseObject.Owner = $mockDatabaseOwner - - # Add the database object to the database collection - $mockDatabaseObjectsWithIncorrectFileNames.Add($newDatabaseObject) - } - #endregion Database Mocks - - #region Server mocks - - $mockBadServerObject = New-Object -TypeName Object - - $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $mockServerObject.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection - $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) - $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) - $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) - $mockServerObject.ComputerNamePhysicalNetBIOS = $mockServerObjectDomainInstanceName - $mockServerObject.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection - $mockServerObject.ConnectionContext.TrueLogin = $mockTrueLogin - $mockServerObject.Databases = $mockDatabaseObjects - $mockServerObject.DomainInstanceName = $mockServerObjectDomainInstanceName - $mockServerObject.NetName = $mockServerObjectDomainInstanceName - $mockServerObject.ServiceName = 'MSSQLSERVER' - $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Primary' - $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Primary' - $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Secondary' - - $mockServer2Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $mockServer2Object.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection - $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) - $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) - $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) - $mockServer2Object.ComputerNamePhysicalNetBIOS = $mockPrimaryServerObjectDomainInstanceName - $mockServer2Object.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection - $mockServer2Object.ConnectionContext.TrueLogin = $mockTrueLogin - $mockServer2Object.Databases = $mockDatabaseObjects - $mockServer2Object.DomainInstanceName = $mockPrimaryServerObjectDomainInstanceName - $mockServer2Object.NetName = $mockPrimaryServerObjectDomainInstanceName - $mockServer2Object.ServiceName = 'MSSQLSERVER' - $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Secondary' - $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Secondary' - $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Primary' - - #endregion Server mocks - - #region Invoke Query Mock - - $mockResultInvokeQueryFileExist = { - return @{ - Tables = @{ - Rows = @{ - 'File is a Directory' = 1 + #endregion Database Mocks + + #region Server mocks + $mockBadServerObject = New-Object -TypeName Object + + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServerObject.ComputerNamePhysicalNetBIOS = $mockServerObjectDomainInstanceName + $mockServerObject.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServerObject.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServerObject.Databases = $mockDatabaseObjects + $mockServerObject.DomainInstanceName = $mockServerObjectDomainInstanceName + $mockServerObject.NetName = $mockServerObjectDomainInstanceName + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Secondary' + + $mockServer2Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServer2Object.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServer2Object.ComputerNamePhysicalNetBIOS = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServer2Object.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServer2Object.Databases = $mockDatabaseObjects + $mockServer2Object.DomainInstanceName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.NetName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ServiceName = 'MSSQLSERVER' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Primary' + + #endregion Server mocks + + #region Invoke Query Mock + + $mockResultInvokeQueryFileExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 1 + } + } } } - } - } - $mockResultInvokeQueryFileNotExist = { - return @{ - Tables = @{ - Rows = @{ - 'File is a Directory' = 0 + $mockResultInvokeQueryFileNotExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 0 + } + } } } - } - } - $mockInvokeQueryParameterRestoreDatabase = { - $Query -like 'RESTORE DATABASE * + $mockInvokeQueryParameterRestoreDatabase = { + $Query -like 'RESTORE DATABASE * FROM DISK = * WITH NORECOVERY' - } + } - $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs = { - $Query -like 'EXECUTE AS LOGIN = * + $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs = { + $Query -like 'EXECUTE AS LOGIN = * RESTORE DATABASE * FROM DISK = * WITH NORECOVERY* REVERT' - } + } + + #endregion Invoke Query Mock - #endregion Invoke Query Mock - Describe 'SqlAGDatabase\Get-TargetResource' { - BeforeAll { Mock -CommandName Connect-SQL -MockWith { return $mockServerObject } -Verifiable Mock -CommandName Import-SQLPSModule -Verifiable } @@ -443,9 +443,336 @@ REVERT' } } - Describe 'SqlAGDatabase\Set-TargetResource' -Tag 'Set' { - Context 'Tests that was moved into its own context block to prevent intermittent fails (see issue #1532) - workaround until proper refactor' { + Describe 'SqlAGDatabase\Set-TargetResource' -Tag 'Set Manual' { + Context 'Tests that was moved into its own context block to prevent intermittent fails (see issue #1532) - workaround until proper refactor with seeding on manual' { BeforeAll { + #region Parameter Mocks + + # The databases defined in the resource + $mockDatabaseNameParameter = @( + 'DB*' + 'AnotherDB' + '3rd*OfDatabase' + '4th*OfDatabase' + ) + + $mockDatabaseNameParameterWithNonExistingDatabases = @( + 'NotFound*' + 'Unknown1' + ) + + $mockBackupPath = 'X:\Backup' + + $mockProcessOnlyOnActiveNode = $false + + #endregion Parameter Mocks + + #region mock names + + $mockServerObjectDomainInstanceName = 'Server1' + $mockPrimaryServerObjectDomainInstanceName = 'Server2' + $mockAvailabilityGroupObjectName = 'AvailabilityGroup1' + $mockAvailabilityGroupWithoutDatabasesObjectName = 'AvailabilityGroupWithoutDatabases' + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName = 'AvailabilityGroup2' + $mockTrueLogin = 'Login1' + $mockDatabaseOwner = 'DatabaseOwner1' + $mockReplicaSeedingMode = 'Manual' + #endregion mock names + + #region Availability Replica Mocks + + $mockAvailabilityReplicaObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplicaCollection + foreach ( $mockAvailabilityReplicaName in @('Server1','Server2') ) + { + $newAvailabilityReplicaObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $newAvailabilityReplicaObject.Name = $mockAvailabilityReplicaName + $newAvailabilityReplicaObject.SeedingMode = 'Manual' + + if ( $mockServerObjectDomainInstanceName -eq $mockAvailabilityReplicaName ) + { + $newAvailabilityReplicaObject.Role = 'Primary' + } + + $mockAvailabilityReplicaObjects.Add($newAvailabilityReplicaObject) + } + + #endregion Availability Replica Mocks + + #region Availability Group Mocks + + $mockAvailabilityDatabaseNames = @( + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockAvailabilityDatabaseAbsentResults = @( + 'DB2' + '3rdTypeOfDatabase' + ) + + $mockAvailabilityDatabaseExactlyAddResults = @( + 'DB1' + ) + + $mockAvailabilityDatabaseExactlyRemoveResults = @( + 'UndefinedDatabase' + ) + + $mockAvailabilityDatabasePresentResults = @( + 'DB1' + ) + + $mockAvailabilityDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseCollection + foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) + { + $newAvailabilityDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabase + $newAvailabilityDatabaseObject.Name = $mockAvailabilityDatabaseName + $mockAvailabilityDatabaseObjects.Add($newAvailabilityDatabaseObject) + } + + $mockBadAvailabilityGroupObject = New-Object -TypeName Object + + $mockAvailabilityGroupObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObject.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObject.Name = $mockAvailabilityGroupObjectName + $mockAvailabilityGroupObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupWithoutDatabasesObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupWithoutDatabasesObject.Name = $mockAvailabilityGroupWithoutDatabasesObjectName + $mockAvailabilityGroupWithoutDatabasesObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupWithoutDatabasesObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.PrimaryReplicaServerName = $mockPrimaryServerObjectDomainInstanceName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + #endregion Availability Group Mocks + + #region Certificate Mocks + + [byte[]]$mockThumbprint1 = @( + 83 + 121 + 115 + 116 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 46 + 85 + 84 + 70 + 56 + 69 + 110 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + [byte[]]$mockThumbprint2 = @( + 83 + 121 + 115 + 23 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 85 + 85 + 84 + 70 + 56 + 69 + 23 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + $mockCertificateObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject1.Thumbprint = $mockThumbprint1 + + $mockCertificateObject2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject2.Thumbprint = $mockThumbprint2 + + $mockDatabaseEncryptionKeyObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseEncryptionKey + $mockDatabaseEncryptionKeyObject.EncryptorName = 'TDE Cert' + $mockDatabaseEncryptionKeyObject.Thumbprint = $mockThumbprint1 + + #endregion Certificate Mocks + + #region Database File Mocks + + $mockDataFilePath = 'E:\SqlData' + $mockLogFilePath = 'F:\SqlLog' + $mockDataFilePathIncorrect = 'G:\SqlData' + $mockLogFilePathIncorrect = 'H:\SqlData' + + #endregion Database File Mocks + + #region Database Mocks + + # The databases found on the instance + $mockPresentDatabaseNames = @( + 'DB1' + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockMasterDatabaseName = 'master' + $mockMasterDatabaseObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $mockMasterDatabaseObject1.Name = $mockMasterDatabaseName + $mockMasterDatabaseObject1.ID = 1 + $mockMasterDatabaseObject1.Certificates = @($mockCertificateObject1) + $mockMasterDatabaseObject1.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockMasterDatabaseName).mdf" ) ) + } + } + $mockMasterDatabaseObject1.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockMasterDatabaseName).ldf" ) ) + } + + $mockDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner + + # Add the database object to the database collection + $mockDatabaseObjects.Add($newDatabaseObject) + } + $mockDatabaseObjects.Add($mockMasterDatabaseObject1) + + $mockDatabaseObjectsWithIncorrectFileNames = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePathIncorrect, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePathIncorrect, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner + + # Add the database object to the database collection + $mockDatabaseObjectsWithIncorrectFileNames.Add($newDatabaseObject) + } + + #endregion Database Mocks + + #region Server mocks + $mockBadServerObject = New-Object -TypeName Object + + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServerObject.ComputerNamePhysicalNetBIOS = $mockServerObjectDomainInstanceName + $mockServerObject.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServerObject.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServerObject.Databases = $mockDatabaseObjects + $mockServerObject.DomainInstanceName = $mockServerObjectDomainInstanceName + $mockServerObject.NetName = $mockServerObjectDomainInstanceName + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Secondary' + + $mockServer2Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServer2Object.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServer2Object.ComputerNamePhysicalNetBIOS = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServer2Object.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServer2Object.Databases = $mockDatabaseObjects + $mockServer2Object.DomainInstanceName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.NetName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ServiceName = 'MSSQLSERVER' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Primary' + + #endregion Server mocks + + #region Invoke Query Mock + + $mockResultInvokeQueryFileExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 1 + } + } + } + } + + $mockResultInvokeQueryFileNotExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 0 + } + } + } + } + + $mockInvokeQueryParameterRestoreDatabase = { + $Query -like 'RESTORE DATABASE * +FROM DISK = * +WITH NORECOVERY' + } + + $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs = { + $Query -like 'EXECUTE AS LOGIN = * +RESTORE DATABASE * +FROM DISK = * +WITH NORECOVERY* +REVERT' + } + + #endregion Invoke Query Mock + + Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServerObject } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServer2Object } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } Mock -CommandName Import-SQLPSModule -Verifiable @@ -1176,10 +1503,1227 @@ REVERT' } } } - } +# } + +# Describe 'SqlAGDatabase\Set-TargetResource' -Tag 'Set Automatic' { + Context 'Tests that was moved into its own context block to prevent intermittent fails (see issue #1532) - workaround until proper refactor with seeding on automatic' { + BeforeAll { + #region Parameter Mocks + + # The databases defined in the resource + $mockDatabaseNameParameter = @( + 'DB*' + 'AnotherDB' + '3rd*OfDatabase' + '4th*OfDatabase' + ) + + $mockDatabaseNameParameterWithNonExistingDatabases = @( + 'NotFound*' + 'Unknown1' + ) + + $mockBackupPath = 'X:\Backup' + + $mockProcessOnlyOnActiveNode = $false + + #endregion Parameter Mocks + + #region mock names + + $mockServerObjectDomainInstanceName = 'Server1' + $mockPrimaryServerObjectDomainInstanceName = 'Server2' + $mockAvailabilityGroupObjectName = 'AvailabilityGroup1' + $mockAvailabilityGroupWithoutDatabasesObjectName = 'AvailabilityGroupWithoutDatabases' + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName = 'AvailabilityGroup2' + $mockTrueLogin = 'Login1' + $mockDatabaseOwner = 'DatabaseOwner1' + $mockReplicaSeedingMode = 'Automatic' + #endregion mock names + + #region Availability Replica Mocks + + $mockAvailabilityReplicaObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplicaCollection + foreach ( $mockAvailabilityReplicaName in @('Server1','Server2') ) + { + $newAvailabilityReplicaObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $newAvailabilityReplicaObject.Name = $mockAvailabilityReplicaName + $newAvailabilityReplicaObject.SeedingMode = $mockReplicaSeedingMode + + if ( $mockServerObjectDomainInstanceName -eq $mockAvailabilityReplicaName ) + { + $newAvailabilityReplicaObject.Role = 'Primary' + } + + $mockAvailabilityReplicaObjects.Add($newAvailabilityReplicaObject) + } + + #endregion Availability Replica Mocks + + #region Availability Group Mocks + + $mockAvailabilityDatabaseNames = @( + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockAvailabilityDatabaseAbsentResults = @( + 'DB2' + '3rdTypeOfDatabase' + ) + + $mockAvailabilityDatabaseExactlyAddResults = @( + 'DB1' + ) + + $mockAvailabilityDatabaseExactlyRemoveResults = @( + 'UndefinedDatabase' + ) + + $mockAvailabilityDatabasePresentResults = @( + 'DB1' + ) + + $mockAvailabilityDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseCollection + foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) + { + $newAvailabilityDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabase + $newAvailabilityDatabaseObject.Name = $mockAvailabilityDatabaseName + $mockAvailabilityDatabaseObjects.Add($newAvailabilityDatabaseObject) + } + + $mockBadAvailabilityGroupObject = New-Object -TypeName Object + + $mockAvailabilityGroupObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObject.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObject.Name = $mockAvailabilityGroupObjectName + $mockAvailabilityGroupObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupWithoutDatabasesObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupWithoutDatabasesObject.Name = $mockAvailabilityGroupWithoutDatabasesObjectName + $mockAvailabilityGroupWithoutDatabasesObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupWithoutDatabasesObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.PrimaryReplicaServerName = $mockPrimaryServerObjectDomainInstanceName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + #endregion Availability Group Mocks + + #region Certificate Mocks + + [byte[]]$mockThumbprint1 = @( + 83 + 121 + 115 + 116 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 46 + 85 + 84 + 70 + 56 + 69 + 110 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + [byte[]]$mockThumbprint2 = @( + 83 + 121 + 115 + 23 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 85 + 85 + 84 + 70 + 56 + 69 + 23 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + $mockCertificateObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject1.Thumbprint = $mockThumbprint1 + + $mockCertificateObject2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject2.Thumbprint = $mockThumbprint2 + + $mockDatabaseEncryptionKeyObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseEncryptionKey + $mockDatabaseEncryptionKeyObject.EncryptorName = 'TDE Cert' + $mockDatabaseEncryptionKeyObject.Thumbprint = $mockThumbprint1 + + #endregion Certificate Mocks + + #region Database File Mocks + + $mockDataFilePath = 'E:\SqlData' + $mockLogFilePath = 'F:\SqlLog' + $mockDataFilePathIncorrect = 'G:\SqlData' + $mockLogFilePathIncorrect = 'H:\SqlData' + + #endregion Database File Mocks + + #region Database Mocks + + # The databases found on the instance + $mockPresentDatabaseNames = @( + 'DB1' + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockMasterDatabaseName = 'master' + $mockMasterDatabaseObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $mockMasterDatabaseObject1.Name = $mockMasterDatabaseName + $mockMasterDatabaseObject1.ID = 1 + $mockMasterDatabaseObject1.Certificates = @($mockCertificateObject1) + $mockMasterDatabaseObject1.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockMasterDatabaseName).mdf" ) ) + } + } + $mockMasterDatabaseObject1.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockMasterDatabaseName).ldf" ) ) + } + + $mockDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner + + # Add the database object to the database collection + $mockDatabaseObjects.Add($newDatabaseObject) + } + $mockDatabaseObjects.Add($mockMasterDatabaseObject1) + + $mockDatabaseObjectsWithIncorrectFileNames = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePathIncorrect, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePathIncorrect, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner + + # Add the database object to the database collection + $mockDatabaseObjectsWithIncorrectFileNames.Add($newDatabaseObject) + } + + #endregion Database Mocks + + #region Server mocks + $mockBadServerObject = New-Object -TypeName Object + + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServerObject.ComputerNamePhysicalNetBIOS = $mockServerObjectDomainInstanceName + $mockServerObject.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServerObject.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServerObject.Databases = $mockDatabaseObjects + $mockServerObject.DomainInstanceName = $mockServerObjectDomainInstanceName + $mockServerObject.NetName = $mockServerObjectDomainInstanceName + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Secondary' + + $mockServer2Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServer2Object.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServer2Object.ComputerNamePhysicalNetBIOS = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServer2Object.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServer2Object.Databases = $mockDatabaseObjects + $mockServer2Object.DomainInstanceName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.NetName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ServiceName = 'MSSQLSERVER' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Primary' + + #endregion Server mocks + + #region Invoke Query Mock + + $mockResultInvokeQueryFileExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 1 + } + } + } + } + + $mockResultInvokeQueryFileNotExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 0 + } + } + } + } + + $mockInvokeQueryParameterRestoreDatabase = { + $Query -like 'RESTORE DATABASE * +FROM DISK = * +WITH NORECOVERY' + } + + $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs = { + $Query -like 'EXECUTE AS LOGIN = * +RESTORE DATABASE * +FROM DISK = * +WITH NORECOVERY* +REVERT' + } + + #endregion Invoke Query Mock + + + Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServerObject } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServer2Object } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Mock -CommandName Import-SQLPSModule -Verifiable + Mock -CommandName Invoke-Query -Verifiable -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Mock -CommandName Invoke-Query -Verifiable -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Mock -CommandName Join-Path -MockWith { [IO.Path]::Combine($databaseMembershipClass.BackupPath,"$($database.Name)_Full_$(Get-Date -Format 'yyyyMMddhhmmss').bak") } -Verifiable -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Mock -CommandName Join-Path -MockWith { [IO.Path]::Combine($databaseMembershipClass.BackupPath,"$($database.Name)_Log_$(Get-Date -Format 'yyyyMMddhhmmss').trn") } -Verifiable -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Mock -CommandName Remove-Item -Verifiable + } + + BeforeEach { + $mockSetTargetResourceParameters = @{ + DatabaseName = $($mockDatabaseNameParameter) + ServerName = $($mockServerObject.DomainInstanceName) + InstanceName = $('MSSQLSERVER') + AvailabilityGroupName = $($mockAvailabilityGroupObjectName) + BackupPath = $($mockBackupPath) + Ensure = 'Present' + Force = $false + MatchDatabaseOwner = $true + ReplaceExisting = $false + } + + Mock -CommandName Add-SqlAvailabilityDatabase -Verifiable -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Mock -CommandName Add-SqlAvailabilityDatabase -Verifiable -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Mock -CommandName Add-SqlAvailabilityDatabase -Verifiable -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Mock -CommandName Add-SqlAvailabilityDatabase -Verifiable -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Mock -CommandName Backup-SqlDatabase -Verifiable -ParameterFilter { $BackupAction -eq 'Database' } + Mock -CommandName Backup-SqlDatabase -Verifiable -ParameterFilter { $BackupAction -eq 'Log'} + Mock -CommandName Connect-SQL -MockWith { return $mockServerObject } -Verifiable -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Mock -CommandName Connect-SQL -MockWith { return $mockServerObject } -Verifiable -ParameterFilter { $ServerName -eq 'Server1' } + Mock -CommandName Connect-SQL -MockWith { return $mockServer2Object } -Verifiable -ParameterFilter { $ServerName -eq 'Server2' } + Mock -CommandName Invoke-Query -MockWith $mockResultInvokeQueryFileExist -Verifiable -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Mock -CommandName Remove-SqlAvailabilityDatabase -Verifiable + Mock -CommandName Test-ImpersonatePermissions -MockWith { $true } -Verifiable + } + + Context 'When Ensure is Present' { + It 'Should add the specified databases to the availability group' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + It 'Should add the specified databases to the availability group when the primary replica is on another server' { + $mockSetTargetResourceParameters.AvailabilityGroupName = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + It 'Should not do anything if no databases were found to add' { + $mockSetTargetResourceParameters.DatabaseName = $mockAvailabilityDatabaseNames + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 0 -Exactly + } + + It 'Should add the specified databases to the availability group when "MatchDatabaseOwner" is $false' { + $mockSetTargetResourceParameters.MatchDatabaseOwner = $false + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 0 -Exactly + } + + It 'Should add the specified databases to the availability group when "ReplaceExisting" is $true' { + $mockSetTargetResourceParameters.DatabaseName = 'DB1' + $mockSetTargetResourceParameters.ReplaceExisting = $true + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + It 'Should throw the correct error when "MatchDatabaseOwner" is $true and the current login does not have impersonate permissions' { + Mock -CommandName Test-ImpersonatePermissions -MockWith { $false } -Verifiable + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw "The login '$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)' is missing impersonate any login, control server, impersonate login, or control login permissions in the instances 'Server2'." + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + $prerequisiteChecks = @{ + RecoveryModel = 'Full' + ReadOnly = $false + UserAccess = 'Multiple' + AutoClose = $false + AvailabilityGroupName = '' + IsMirroringEnabled = $false + } + + foreach ( $prerequisiteCheck in $prerequisiteChecks.GetEnumerator() ) + { + It "Should throw the correct error when the database property '$($prerequisiteCheck.Key)' is not '$($prerequisiteCheck.Value)'" { + $originalValue = $mockServerObject.Databases['DB1'].($prerequisiteCheck.Key) + $mockServerObject.Databases['DB1'].($prerequisiteCheck.Key) = $true + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw "The operation on the database 'DB1' failed with the following errors: The following prerequisite checks failed: $($prerequisiteCheck.Key) is not $($prerequisiteCheck.Value)." + + foreach ( $databaseProperty in $prerequisiteChecks.GetEnumerator() ) + { + if ( $prerequisiteCheck.Key -eq $databaseProperty.Key ) + { + $mockServerObject.Databases['DB1'].($databaseProperty.Key) | Should -Not -Be ($databaseProperty.Value) + } + else + { + $mockServerObject.Databases['DB1'].($databaseProperty.Key) | Should -Be ($databaseProperty.Value) + } + } + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + + $mockServerObject.Databases['DB1'].($prerequisiteCheck.Key) = $originalValue + } + } + + It 'Should throw the correct error when the database property "ID" is less than "4"' { + $mockSetTargetResourceParameters.DatabaseName = @('master') + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw "The operation on the database 'master' failed with the following errors: The following prerequisite checks failed: The database cannot be a system database." + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + $filestreamProperties = @{ + DefaultFileStreamFileGroup = '' + FilestreamDirectoryName = '' + FilestreamNonTransactedAccess = 'Off' + } + + foreach ( $filestreamProperty in $filestreamProperties.GetEnumerator() ) + { + It "Should throw the correct error 'AlterAvailabilityGroupDatabaseMembershipFailure' when the database property '$($filestreamProperty.Key)' is not '$($filestreamProperty.Value)'" { + $originalValue = $mockServerObject.Databases['DB1'].($filestreamProperty.Key) + $mockServerObject.Databases['DB1'].($filestreamProperty.Key) = 'On' + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw "The operation on the database 'DB1' failed with the following errors: The following prerequisite checks failed: Filestream is disabled on the following instances: Server2" + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + + $mockServerObject.Databases['DB1'].($filestreamProperty.Key) = $originalValue + } + } + + It 'Should throw the correct error when the database property "ContainmentType" is not "Partial"' { + $originalValue = $mockServerObject.Databases['DB1'].ContainmentType + $mockServerObject.Databases['DB1'].ContainmentType = 'Partial' + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw "The operation on the database 'DB1' failed with the following errors: The following prerequisite checks failed: Contained Database Authentication is not enabled on the following instances: " + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + + $mockServerObject.Databases['DB1'].ContainmentType = $originalValue + } + + It 'Should throw the correct error when the database file path does not exist on the secondary replica' { + Mock -CommandName Invoke-Query -MockWith $mockResultInvokeQueryFileNotExist -Verifiable -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + $originalValue = $mockServer2Object.Databases['DB1'].FileGroups.Files.FileName + $mockServer2Object.Databases['DB1'].FileGroups.Files.FileName = ( [IO.Path]::Combine( 'X:\', "DB1.mdf" ) ) + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw "The operation on the database 'DB1' failed with the following errors: The following prerequisite checks failed: The instance 'Server2' is missing the following directories: X:\, F:\SqlLog" + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + + $mockServer2Object.Databases['DB1'].FileGroups.Files.FileName = $originalValue + } + + It 'Should throw the correct error when the log file path does not exist on the secondary replica' { + Mock -CommandName Invoke-Query -MockWith $mockResultInvokeQueryFileNotExist -Verifiable -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + $originalValue = $mockServer2Object.Databases['DB1'].LogFiles.FileName + $mockServer2Object.Databases['DB1'].LogFiles.FileName = ( [IO.Path]::Combine( 'Y:\', "DB1.ldf" ) ) + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw 'The operation on the database ''DB1'' failed with the following errors: The following prerequisite checks failed: The instance ''Server2'' is missing the following directories: E:\SqlData, Y:\' + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + + $mockServer2Object.Databases['DB1'].LogFiles.FileName = $originalValue + } + + It 'Should throw the correct error when TDE is enabled on the database but the certificate is not present on the replica instances' { + $mockServerObject.Databases['DB1'].EncryptionEnabled = $true + $mockServerObject.Databases['DB1'].DatabaseEncryptionKey = $mockDatabaseEncryptionKeyObject + $mockServer2Object.Databases['master'].Certificates = @($mockCertificateObject2) + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw 'The operation on the database ''DB1'' failed with the following errors: The following prerequisite checks failed: The instance ''Server2'' is missing the following certificates: TDE Cert' + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + + $mockServerObject.Databases['DB1'].EncryptionEnabled = $false + $mockServerObject.Databases['DB1'].DatabaseEncryptionKey = $null + $mockServer2Object.Databases['master'].Certificates = @($mockCertificateObject1) + } + + It 'Should add the specified databases to the availability group when the database has not been previously backed up' { + $mockServerObject.Databases['DB1'].LastBackupDate = 0 + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + It 'Should throw the correct error when it fails to add the database to the primary replica' { + Mock -CommandName Add-SqlAvailabilityDatabase -MockWith { throw } -Verifiable -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw 'The operation on the database ''DB1'' failed with the following errors: System.Management.Automation.RuntimeException: ScriptHalted' + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 2 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + + It 'Should throw the correct error when it fails to add the database to the primary replica' { + Mock -CommandName Add-SqlAvailabilityDatabase -MockWith { throw } -Verifiable -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw 'The operation on the database ''DB1'' failed with the following errors: System.Management.Automation.RuntimeException: ScriptHalted' + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + } + + Context 'When Ensure is Present and Force is True' { + BeforeEach { + $mockSetTargetResourceParameters.Ensure = 'Present' + $mockSetTargetResourceParameters.Force = $true + } + + It 'Should ensure the database membership of the availability group is exactly as specified' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server1' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Primary' } + Assert-MockCalled -CommandName Add-SqlAvailabilityDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $InputObject.PrimaryReplicaServerName -eq 'Server2' -and $InputObject.LocalReplicaRole -eq 'Secondary' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Database' } + Assert-MockCalled -CommandName Backup-SqlDatabase -Scope It -Times 0 -Exactly -ParameterFilter { $BackupAction -eq 'Log' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' -and $InstanceName -eq 'MSSQLSERVER' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $ServerName -eq 'Server1' } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 3 -Exactly -ParameterFilter { $ServerName -eq 'Server2' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 1 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } + Assert-MockCalled -CommandName Get-PrimaryReplicaServerObject -Scope It -Times 0 -Exactly -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 2 -Exactly -ParameterFilter { $Query -like 'EXEC master.dbo.xp_fileexist *' } + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabase + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly -ParameterFilter $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Full_*.bak' } + Assert-MockCalled -CommandName Join-Path -Scope It -Times 0 -Exactly -ParameterFilter { $ChildPath -like '*_Log_*.trn' } + Assert-MockCalled -CommandName Remove-Item -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityDatabase -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-ImpersonatePermissions -Scope It -Times 1 -Exactly + } + } + } + } + + Describe 'SqlAGDatabase\Test-TargetResource' { + BeforeAll { + #region Parameter Mocks + + # The databases defined in the resource + $mockDatabaseNameParameter = @( + 'DB*' + 'AnotherDB' + '3rd*OfDatabase' + '4th*OfDatabase' + ) + + $mockDatabaseNameParameterWithNonExistingDatabases = @( + 'NotFound*' + 'Unknown1' + ) + + $mockBackupPath = 'X:\Backup' + + $mockProcessOnlyOnActiveNode = $false + + #endregion Parameter Mocks + + #region mock names + + $mockServerObjectDomainInstanceName = 'Server1' + $mockPrimaryServerObjectDomainInstanceName = 'Server2' + $mockAvailabilityGroupObjectName = 'AvailabilityGroup1' + $mockAvailabilityGroupWithoutDatabasesObjectName = 'AvailabilityGroupWithoutDatabases' + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName = 'AvailabilityGroup2' + $mockTrueLogin = 'Login1' + $mockDatabaseOwner = 'DatabaseOwner1' + $mockReplicaSeedingMode = 'Manual' + #endregion mock names + + #region Availability Replica Mocks + + $mockAvailabilityReplicaObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplicaCollection + foreach ( $mockAvailabilityReplicaName in @('Server1','Server2') ) + { + $newAvailabilityReplicaObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $newAvailabilityReplicaObject.Name = $mockAvailabilityReplicaName + $newAvailabilityReplicaObject.SeedingMode = 'Manual' + + if ( $mockServerObjectDomainInstanceName -eq $mockAvailabilityReplicaName ) + { + $newAvailabilityReplicaObject.Role = 'Primary' + } + + $mockAvailabilityReplicaObjects.Add($newAvailabilityReplicaObject) + } + + #endregion Availability Replica Mocks + + #region Availability Group Mocks + + $mockAvailabilityDatabaseNames = @( + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockAvailabilityDatabaseAbsentResults = @( + 'DB2' + '3rdTypeOfDatabase' + ) + + $mockAvailabilityDatabaseExactlyAddResults = @( + 'DB1' + ) + + $mockAvailabilityDatabaseExactlyRemoveResults = @( + 'UndefinedDatabase' + ) + + $mockAvailabilityDatabasePresentResults = @( + 'DB1' + ) + + $mockAvailabilityDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseCollection + foreach ( $mockAvailabilityDatabaseName in $mockAvailabilityDatabaseNames ) + { + $newAvailabilityDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityDatabase + $newAvailabilityDatabaseObject.Name = $mockAvailabilityDatabaseName + $mockAvailabilityDatabaseObjects.Add($newAvailabilityDatabaseObject) + } + + $mockBadAvailabilityGroupObject = New-Object -TypeName Object + + $mockAvailabilityGroupObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObject.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObject.Name = $mockAvailabilityGroupObjectName + $mockAvailabilityGroupObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupWithoutDatabasesObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupWithoutDatabasesObject.Name = $mockAvailabilityGroupWithoutDatabasesObjectName + $mockAvailabilityGroupWithoutDatabasesObject.PrimaryReplicaServerName = $mockServerObjectDomainInstanceName + $mockAvailabilityGroupWithoutDatabasesObject.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityDatabases = $mockAvailabilityDatabaseObjects + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name = $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServerName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.PrimaryReplicaServerName = $mockPrimaryServerObjectDomainInstanceName + $mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.AvailabilityReplicas = $mockAvailabilityReplicaObjects + + #endregion Availability Group Mocks + + #region Certificate Mocks + + [byte[]]$mockThumbprint1 = @( + 83 + 121 + 115 + 116 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 46 + 85 + 84 + 70 + 56 + 69 + 110 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + [byte[]]$mockThumbprint2 = @( + 83 + 121 + 115 + 23 + 101 + 109 + 46 + 84 + 101 + 120 + 116 + 85 + 85 + 84 + 70 + 56 + 69 + 23 + 99 + 111 + 100 + 105 + 110 + 103 + ) + + $mockCertificateObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject1.Thumbprint = $mockThumbprint1 + + $mockCertificateObject2 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Certificate + $mockCertificateObject2.Thumbprint = $mockThumbprint2 + + $mockDatabaseEncryptionKeyObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseEncryptionKey + $mockDatabaseEncryptionKeyObject.EncryptorName = 'TDE Cert' + $mockDatabaseEncryptionKeyObject.Thumbprint = $mockThumbprint1 + + #endregion Certificate Mocks + + #region Database File Mocks + + $mockDataFilePath = 'E:\SqlData' + $mockLogFilePath = 'F:\SqlLog' + $mockDataFilePathIncorrect = 'G:\SqlData' + $mockLogFilePathIncorrect = 'H:\SqlData' + + #endregion Database File Mocks + + #region Database Mocks + + # The databases found on the instance + $mockPresentDatabaseNames = @( + 'DB1' + 'DB2' + '3rdTypeOfDatabase' + 'UndefinedDatabase' + ) + + $mockMasterDatabaseName = 'master' + $mockMasterDatabaseObject1 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $mockMasterDatabaseObject1.Name = $mockMasterDatabaseName + $mockMasterDatabaseObject1.ID = 1 + $mockMasterDatabaseObject1.Certificates = @($mockCertificateObject1) + $mockMasterDatabaseObject1.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockMasterDatabaseName).mdf" ) ) + } + } + $mockMasterDatabaseObject1.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockMasterDatabaseName).ldf" ) ) + } + + $mockDatabaseObjects = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePath, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePath, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner + + # Add the database object to the database collection + $mockDatabaseObjects.Add($newDatabaseObject) + } + $mockDatabaseObjects.Add($mockMasterDatabaseObject1) + + $mockDatabaseObjectsWithIncorrectFileNames = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabaseCollection + foreach ( $mockPresentDatabaseName in $mockPresentDatabaseNames ) + { + $newDatabaseObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database + $newDatabaseObject.Name = $mockPresentDatabaseName + $newDatabaseObject.FileGroups = @{ + Name = 'PRIMARY' + Files = @{ + FileName = ( [IO.Path]::Combine( $mockDataFilePathIncorrect, "$($mockPresentDatabaseName).mdf" ) ) + } + } + $newDatabaseObject.LogFiles = @{ + FileName = ( [IO.Path]::Combine( $mockLogFilePathIncorrect, "$($mockPresentDatabaseName).ldf" ) ) + } + $newDatabaseObject.Owner = $mockDatabaseOwner + + # Add the database object to the database collection + $mockDatabaseObjectsWithIncorrectFileNames.Add($newDatabaseObject) + } + + #endregion Database Mocks + + #region Server mocks + $mockBadServerObject = New-Object -TypeName Object + + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServerObject.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServerObject.ComputerNamePhysicalNetBIOS = $mockServerObjectDomainInstanceName + $mockServerObject.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServerObject.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServerObject.Databases = $mockDatabaseObjects + $mockServerObject.DomainInstanceName = $mockServerObjectDomainInstanceName + $mockServerObject.NetName = $mockServerObjectDomainInstanceName + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Primary' + $mockServerObject.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Secondary' + + $mockServer2Object = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServer2Object.AvailabilityGroups = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroupCollection + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupWithoutDatabasesObject.Clone()) + $mockServer2Object.AvailabilityGroups.Add($mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Clone()) + $mockServer2Object.ComputerNamePhysicalNetBIOS = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ConnectionContext = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockServer2Object.ConnectionContext.TrueLogin = $mockTrueLogin + $mockServer2Object.Databases = $mockDatabaseObjects + $mockServer2Object.DomainInstanceName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.NetName = $mockPrimaryServerObjectDomainInstanceName + $mockServer2Object.ServiceName = 'MSSQLSERVER' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupWithoutDatabasesObject.Name].LocalReplicaRole = 'Secondary' + $mockServer2Object.AvailabilityGroups[$mockAvailabilityGroupObjectWithPrimaryReplicaOnAnotherServer.Name].LocalReplicaRole = 'Primary' + + #endregion Server mocks + + #region Invoke Query Mock + + $mockResultInvokeQueryFileExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 1 + } + } + } + } + + $mockResultInvokeQueryFileNotExist = { + return @{ + Tables = @{ + Rows = @{ + 'File is a Directory' = 0 + } + } + } + } + + $mockInvokeQueryParameterRestoreDatabase = { + $Query -like 'RESTORE DATABASE * +FROM DISK = * +WITH NORECOVERY' + } + + $mockInvokeQueryParameterRestoreDatabaseWithExecuteAs = { + $Query -like 'EXECUTE AS LOGIN = * +RESTORE DATABASE * +FROM DISK = * +WITH NORECOVERY* +REVERT' + } + + #endregion Invoke Query Mock + - Describe 'SqlAGDatabase\Test-TargetResource' { - BeforeAll { Mock -CommandName Connect-SQL -MockWith { return $mockServerObject } -Verifiable Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServerObject } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server1' } Mock -CommandName Get-PrimaryReplicaServerObject -MockWith { return $mockServer2Object } -Verifiable -ParameterFilter { $AvailabilityGroup.PrimaryReplicaServerName -eq 'Server2' } diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index de84deb51..f2e56a65c 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -58,6 +58,15 @@ public enum AvailabilityReplicaAvailabilityMode Unknown } + // TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityReplicaSeedingMode + // Used by: + // SqlAGDatabase + public enum AvailabilityReplicaSeedingMode + { + Automatic, + Manual + } + // TypeName: Microsoft.SqlServer.Management.Smo.EndpointType // Used by: // SqlEndpoint @@ -699,6 +708,7 @@ public FailedOperationException (string message, SmoException inner) : base (mes // BaseType: Microsoft.SqlServer.Management.Smo.NamedSmoObject // Used by: // SqlAG + // SqlAGDatabase public class AvailabilityGroup { public AvailabilityGroup() @@ -750,6 +760,7 @@ public AvailabilityGroup Clone() // BaseType: Microsoft.SqlServer.Management.Smo.NamedSmoObject // Used by: // SqlAG + // SqlAGDatabase public class AvailabilityReplica { public AvailabilityReplica() @@ -764,6 +775,7 @@ public AvailabilityReplica( AvailabilityGroup availabilityGroup, string name ) public string ConnectionModeInSecondaryRole; public string EndpointUrl; public string FailoverMode; + public string SeedingMode; public string Name; public string ReadOnlyRoutingConnectionUrl; public System.Collections.Specialized.StringCollection ReadOnlyRoutingList;