Powershell SQL Server database restore (with CDC) - sql-server

I am trying to create a Powershell script to restore database to my laptop from my desktop. I have a script which creates the backup files and have almost got the restore script working apart from a strange error I get with a CDC enabled database. What I end up with is an off-line single user database. I have to bring it back on-line and change it back to multi-user manually. Here are the relevant bits of my powershell code ...
$instance = "(local)"
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $instance
$restore = New-Object Microsoft.SqlServer.Management.Smo.Restore
$restore.Action = "Database"
$restore.Database = $dbname
$restore.NoRecovery = $false
$restore.ReplaceDatabase = $true
$restore.Devices.AddDevice($filename, "File")
$restore.SqlRestore($server)
I get an error message saying ...
*System.Data.SqlClient.SqlException: Could not update the metadata that indicates database xxxxx is not enabled for Change Data Capture. The failure occurred when executing the command '[sys].[sp_MScdc_ddl_database triggers 'drop''. The error returned was 15517: 'Cannot execute as the database principal because the principal "dbo" does not exist, this type of principal cannot be impersonated, or you do not have permission'*
and a bit further down ...
*The database has been left offline. See the topic MSSQL_ENG003165 in SQL Server Books Online.*
further down ...
Converting database 'xxxxx' from version 655 to the current version 661.Database 'xxxxx' running the upgrade step from version 655 to version 660.Database 'xxxxx' running the upgrade step from version 660 to 661.
While I can get the database back to a useable state, I would ideally like to have it completely scripted. The idea of this is that I can run the backup script on my desktop and then run the restore script on my laptop, which then restores the databases on my laptop so I have a working copy of the same database for when I need to work remotely.
Any insights would be great, even better if someone has come across and solved the same problem.

When I had to set an explicit CDC setting in a restore script, I did something like:
$script_lines = $restore.script( $server )
$script_lines += ', keep_cdc'
$script = ''
foreach ($line in $script_lines) {
$script += $line
}
$script
invoke-sqlcmd -ServerInstance $server.name -Query $script -QueryTimeout 65535

Related

How to run an Azure SQL Server Stored Procedure using an Automation Account Runbook with Managed Identity?

I am trying to run SQL Server DB index and statistics maintenance activating a stored procedure called AzureSQLMaintenance with a PowerShell Runbook in Automation Account.
Since I don't want to use standard SQL Server authentication, I am trying to use Managed Identities.
Online I found some Microsoft documentation getting quite close to the point on Microsoft Tech Community here and here, but both the threads are missing some pieces. A very good clue clue was given to me by this blog post but it was missing Managed Identity Authentication
I finally managed, after a couple of days of tests, to make it work, so I'll write the whole process down in case anybody will need to do the same:
Under Account Settings > Identity System Assigned Managed Identity must be set to On, and we'll need the Object (principal) Id, so remember to mark it down
In my Azure SQL Database Server, under Settings > Azure Active Directory, we'll need to check the value of the Azure Active Directory admin. In my case, this is a group
In Azure Active Directory, edit the group individuated on the previous step and add the Object (principal) Id obtained at the step 1 as a member of the group
A Powershell Runbook in Automation Account needs to be created
The powershell Runbook Code will need to look something like
Write-Output "Run started"
# Instantiate the connection to the SQL Database
$sqlConnection = new-object System.Data.SqlClient.SqlConnection
# Connect to the the account used the Managed Identity
Connect-AzAccount -Identity
# Get a Token
$token = (Get-AzAccessToken -ResourceUrl https://database.windows.net ).Token
# Initialize the connection String
$sqlConnection.ConnectionString = "Data Source=db-server-name.database.windows.net;Initial Catalog=<db-name>;Connect Timeout=60"
# Set the Token to be used by the connection
$sqlConnection.AccessToken = $token
# Open the connection
$sqlConnection.Open()
Write-Output "Azure SQL database connection opened"
# Define the SQL command to run
$sqlCommand = new-object System.Data.SqlClient.SqlCommand
# Allow Long Executions
$sqlCommand.CommandTimeout = 0
# Associate the created connection
$sqlCommand.Connection = $sqlConnection
Write-Output "Issuing command to run stored procedure"
# Execute the SQL command
$sqlCommand.CommandText= 'exec [dbo].[AzureSQLMaintenance] #parameter1 = ''param1Value'', #parameter2 = ''param2Value'' '
$result = $sqlCommand.ExecuteNonQuery()
Write-Output "Stored procedure execution completed"
# Close the SQL connection
$sqlConnection.Close()
Write-Output "Run completed"
At this point, run a test on the Runbook: in my case it worked perfectly, even if it took a while (that's the reason for the parameter $sqlCommand.CommandTimeout = 0)

Providing local variable to Invoke-Command using $using:Variablename

I'm trying to perform unattended SQL Installation using configuration file and I'm using $Using:Password to pass the variable from local machine to the invoke-command but it seems that it is not taking the credentials.
When I remove the /SQLSVCPASSWORD then it works fine.
Anything I'm missing here?
$SQLNodes = #("SQL10")
Foreach ($node in $SQLNodes ){
$Password = "Verystrongpassword"
if(Test-Connection -ComputerName $node -Count 1 -ea 0)
{
$Invoke = Invoke-Command -ComputerName $node {
$Exe = 'C:\SQLServerFull\Setup.exe'
Start-Process -FilePath $Exe -ArgumentList /ConfigurationFile="C:\Script\ConfigurationFile.INI", /SQLSVCPASSWORD=$($using:Password) -Wait -RedirectStandardOutput C:\error.txt
}
}
}
Below is the error
SQL Server 2017 transmits information about your installation experience, as well as other usage and performance data, to Microsoft to help improve the product. To learn more about SQL Server 2017 data processing and privacy controls, please see the Privacy Statement.
The following error occurred:
The SQL Server service account login or password is not valid. Use SQL Server Configuration Manager to update the service account.
Error result: -2061893563
Result facility code: 1306
Result error code: 69

Encryption PowerShell script generated by SQL Server Management Studio executes but doesn't work

In SSMS I tried adding encryption to columns. If I do this in the dialog box, the process works and the field is encrypted. If I try generating a PowerShell script to set the encryption, after many error messages I've finally got the code to execute without error but nothing happens in the database. This is the generated script with all the defaults and standard things and I'm running PowerShell as an administrator.
Import-Module SqlServer
# Set up connection and database SMO objects
$sqlConnectionString = "Data Source=MyPC\SQLEXPRESS;Initial Catalog=MyDatabase;Integrated Security=True;MultipleActiveResultSets=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;Packet Size=4096;Application Name=`"Microsoft SQL Server Management Studio`""
$smoDatabase = Get-SqlDatabase -ConnectionString $sqlConnectionString
# If your encryption changes involve keys in Azure Key Vault, uncomment one of the lines below in order to authenticate:
# * Prompt for a username and password:
#Add-SqlAzureAuthenticationContext -Interactive
# * Enter a Client ID, Secret, and Tenant ID:
#Add-SqlAzureAuthenticationContext -ClientID '<Client ID>' -Secret '<Secret>' -Tenant '<Tenant ID>'
# Change encryption schema
$encryptionChanges = #()
# Add changes for table [dbo].[MyTable]
$encryptionChanges += New-SqlColumnEncryptionSettings -ColumnName dbo.MyTable.MyField -EncryptionType Randomized -EncryptionKey "CEK_Auto1"
Set-SqlColumnEncryption -ColumnEncryptionSettings $encryptionChanges -InputObject $smoDatabase
Any ideas why the code runs but doesn't encrypt the columns?
https://learn.microsoft.com/en-us/sql/powershell/download-sql-server-ps-module?view=sql-server-2017
https://learn.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-column-encryption-using-powershell?view=sql-server-ver15
Following the instructions above with some modifications
Run PowerShell as admin
To give permissions in this session for the file to be executed
This should give correct permission
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
To install SQL Server modules so it PowerShell can access it
Install-Module -Name SqlServer -AllowClobber
View available commands in SqlServer module
Get-Command -Module SqlServer
If New-SqlColumnEncryptionSettings isn't visible, copy files from
sqlserver.21.1.18218.nupkg.zip
downloaded from
https://www.powershellgallery.com/packages/SqlServer/21.1.18218
to
C:\Program Files\WindowsPowerShell\Modules\SqlServer\21.1.18218
Then run the powershell script using
.\test.ps1

Setting a database offline with powershell

I'm working on an automated database restore script in powershell and trying to harden it. At a high level, it goes something like this:
$s = new-object microsoft.sqlserver.management.smo.server '.';
$db = $s.Databases['myDb'];
$db.SetOffline();
# start restore here
The problem here is that if there are any connections in the database, the SetOffline() command gets blocked. I know that there are KillDatabase() and KillAllProcesses() methods on the Server object, but the former sets me up to have to zero out the log file again and the latter has a race condition on a busy server. What I'm looking for is the SMO equivalent of alter database [myDb] set offline with rollback immediate;.
Does such a thing exist? I could resort to doing some ExecuteNonQuery() shenanigans, but it seems like there should be a more elegant way.
So what if you block the access to SQL in the firewall first and then KillAllProcesses?
Then nothing will be able to reconnect and there will be no race condition...
New-NetFirewallRule -DisplayName BlockSQL -Service MSSQL -Direction Inbound -Action Block -Enabled True
You would of course need to call the SetOffline method locally then since MSSQL is blocked from any comunication over the network.
And when you're done taking the DB:s offline, just delete the firewall rule...
Remove-NetFirewallRule -DisplayName BlockSQL

How can I use invoke-sqlcmd without enabling named pipes?

I am using a script that loads the following SQL Server 2008 R2 powershell plugins
Add-PSSnapin SqlServerCmdletSnapin100
Add-PSSnapin SqlServerProviderSnapin100
I then user invoke-sql like this:
Invoke-Sqlcmd -Query "select * from table" -ServerInstance xyz -Database abc -username xxxxxx -password yyyyyyy
I am using method to run a number of upgrade scripts on our databases. I was quite happily using this in our dev\test environments but then we I tried it in production and it turns out we have a difference in server configurations. On our prod servers named pipes are disabled for security reasons (apparently worm attacks) and our DBA's don't want to enable.
This is the error I get and research says it is a named pipes problem - starts working when I enable them too.
INFO ERROR: Invoke-Sqlcmd : A connection was successfully
established with the server, but then an error occurred during the
login process. (provider: Shared Memory Provider, error: 0 - No
process is on the other end of the pipe.)
Does anyone know if there is some way to switch my script so that it does not require named pipes? Or is this the built in connection method for invoke-sqlcmd and I need to change tack (if so any suggestions).
Similar to Surreal's response to use LPC (local shared memory), for TCP/IP instead of named pipes you can also specify -ServerInstance tcp:foodb
This is an educated guess. But here goes:
I think you have to "override the default" by using the registry.
http://support.microsoft.com/kb/229929
Now, the easiest way to do this (IIRC) is to go through your
Control Panel / ODBC Data Source / System DSN.
Add a "Sql Server". (Not the native client ones).
The most important button is the "Client Configuration" where you can pick named-pipes or tcp/ip.
Try out the DSN method, and after completing the wizard, look at the registry entries under
HKEY_LOCAL_MACHINE\Software\Microsoft\MSSQLServer\Client\ConnectTo
.........
You might check out this:
http://sev17.com/2012/11/05/cloning-sql-servers-to-a-test-environment/
Look for this code.
sqlcmd -S myCMServerInstance -d msdb -Q $query -h -1 -W |
foreach { Set-ItemProperty -Path 'HKLM:SOFTWAREMicrosoftMSSQLServerClientConnectTo' -Name $($_ -replace 'TEST') -Value "DBMSSOCN,$_" }
}
You can change the connection method using prefixes to the instance name as for sqlcmd. With SQL Server 2012 and Powershell 4, this works for me:
Invoke-Sqlcmd -Query $sqlQuery -serverinstance "lpc:localhost" -Database "myDatabase"

Resources