Change instance level collation of SQL Server using powershell - sql-server

I want to change the collation of SQL Server instance programmatically using powershell script. Followings are the manual steps:
Stop the SQL Server instance
Go to directory location: "C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\Binn"
Execute following command: sqlservr -c -m -T4022 -T3659 -s"SQL2017" -q"SQL_Latin1_General_CP1_CI_AS"
After the execution of the above command, following message displayed: "The default collation was successfully changed."
Then I need to press ctrl+c to stop further execution. How can I do
this programmatically?

When we execute the command to change the SQL Server Collation, it logs the execution details in event viewer application logs. Using loop we can check the event viewer application logs for SqlServr.exe continuously, and when it generates the following log message: "The default collation was successfully changed", we can kill the process.
#Take the time stamp before execution of Collation Change Command
$StartDateTime=(Get-Date).AddMinutes(-1)
# Execute the Collation Change Process
Write-Host "Executing SQL Server Collation Change Command"
$CollationChangeProcess=Start-Process -FilePath $SQLRootDirectory -ArgumentList
"-c -m -T 4022 -T 3659 -s $JustServerInstanceName -q $NewCollationName" -
NoNewWindow -passthru
Do
{
$log=Get-WinEvent -FilterHashtable #{logname='application';
providername=$SQLServiceName; starttime = $StartDateTime} | Where-Object -
Property Message -Match 'The default collation was successfully changed.'
IF($log.count -gt 0 -and $log.TimeCreated -gt $StartDateTime )
{
Stop-Process -ID $CollationChangeProcess.ID
write-host 'Collation Change Process Completed Successfully.'
break
}
$DateTimeNow=(Get-Date)
$Duration=$DateTimeNow-$StartDateTime
write-host $Duration.totalminutes
Start-Sleep -Seconds 2
IF ($Duration.totalminutes -gt 2)
{
write-host 'Collation Change Process Failed.'
break
}
}while (1 -eq 1)

Thanks #Murali Dhar Darshan. I've made some changes to your solution to make it easier to use. (I don't have high enough reputation to add this as a comment to your answer).
# Params
$NewCollationName="Danish_Norwegian_CI_AS"
$SQLRootDirectory="C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\Binn\sqlservr.exe"
$SQLServiceName="MSSQLSERVER"
# Stop running SQL instance
net stop $SQLServiceName
#Take the time stamp before execution of Collation Change Command
$StartDateTime=(Get-Date).AddMinutes(-1)
# Execute the Collation Change Process
Write-Host "Executing SQL Server Collation Change Command"
$CollationChangeProcess=Start-Process -FilePath $SQLRootDirectory -ArgumentList "-c -m -T 4022 -T 3659 -q $NewCollationName" -NoNewWindow -passthru
Do
{
$log=Get-WinEvent -FilterHashtable #{logname='application';
providername=$SQLServiceName; starttime = $StartDateTime} | Where-Object -Property Message -Match 'The default collation was successfully changed.'
IF($log.count -gt 0 -and $log.TimeCreated -gt $StartDateTime )
{
Stop-Process -ID $CollationChangeProcess.ID
write-host 'Collation Change Process Completed Successfully.'
# Start SQL instance again
net start $SQLServiceName
break
}
$DateTimeNow=(Get-Date)
$Duration=$DateTimeNow-$StartDateTime
write-host $Duration.totalminutes
Start-Sleep -Seconds 2
IF ($Duration.totalminutes -gt 2)
{
write-host 'Collation Change Process Failed.'
Stop-Process -ID $CollationChangeProcess.ID
break
}
}while (1 -eq 1)

Related

Docker-compose SQL Server Create then attach_dbs (windows)

The end game is to create a database when building a docker container, and persist the data so that if the container is removed, I can start the container again and have my database with my persisted data.
I'm using microsoft/mssql-server-windows-developer with Windows containers with docker-compose.
The relevant part of my docker-compose file is (other services removed):
version: "3.9" services:
db:
build:
context: .
dockerfile: Database/Dockerfile
volumes:
- C:\temp:C:\temp
ports:
- 1433:1433
Basically, the db Dockerfile runs a powershell script (very similar to https://github.com/microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/start.ps1). My powershell script starts MSSQLSERVER then runs sql files to create a database, run create table, create procs, etc scripts.
All of this works. docker-compose build then docker-compose up will create and run my database on localhost and everything is great. But, if I manipulate the data at all and remove the database then call docker-compose up again, my data is gone.
Everything I've read about persisting data includes using attach_db. I would like to do some sort of if exists, attach_db else create database.
The question (finally)... Why don't I have an mdf file after I create the database? Am I supposed to? I've messed with different ways to add volumes but my volume is always empty. It doesn't appear I'm creating an mdf file to add to my volume.
EDIT - Adding Dockerfile and ps script Dockerfile calls
Dockerfile:
FROM microsoft/mssql-server-windows-developer
ENV sa_password="nannynannybooboo" \
ACCEPT_EULA="Y" \
db1="db1" \
db2="db2"
EXPOSE 1433
RUN mkdir -p ./db1
RUN mkdir -p ./db2
COPY /Database/startsql.ps1 .
COPY /Database/db1/ ./db1
COPY /Database/db2/ ./db2
HEALTHCHECK CMD [ "sqlcmd", "-Q", "select 2" ]
RUN .\startsql -sa_password $env:sa_password -ACCEPT_EULA $env:ACCEPT_EULA -db_name $env:db2 -Verbose
RUN .\startsql -sa_password $env:sa_password -ACCEPT_EULA $env:ACCEPT_EULA -db_name $env:db1 -Verbose
startsql.ps1
# based off https://github.com/microsoft/mssql-docker/blob/master/windows/mssql-server-windows-developer/start.ps1
param(
[Parameter(Mandatory=$false)]
[string]$sa_password,
[Parameter(Mandatory=$false)]
[string]$ACCEPT_EULA,
[Parameter(Mandatory=$true)]
[string]$db_name
)
if($ACCEPT_EULA -ne "Y" -And $ACCEPT_EULA -ne "y")
{
Write-Verbose "ERROR: You must accept the End User License Agreement before this container can start."
Write-Verbose "Set the environment variable ACCEPT_EULA to 'Y' if you accept the agreement."
exit 1
}
# start the service
Write-Verbose "Starting SQL Server"
start-service MSSQLSERVER
if($sa_password -eq "_") {
if (Test-Path $env:sa_password_path) {
$sa_password = Get-Content -Raw $secretPath
}
else {
Write-Verbose "WARN: Using default SA password, secret file not found at: $secretPath"
}
}
Write-Verbose $sa_password
if($sa_password -ne "_")
{
Write-Verbose "Changing SA login credentials"
$sqlcmd = "ALTER LOGIN sa with password=" +"'" + $sa_password + "'" + ";ALTER LOGIN sa ENABLE;"
& sqlcmd -Q $sqlcmd
}
Write-Verbose "Started SQL Server"
Write-Verbose "Starting set up scripts..."
Write-Verbose $db_name
$exists = $true
$exists = #($sqlServer.Databases | % { $_.Name }) -contains $db_name
$creation = ".\"+$db_name+"\creation.sql"
$creation_rpt = ".\"+$db_name+"\creation.rpt"
$userdefined = ".\"+$db_name+"\userdefined.sql"
$userdefined_rpt = ".\"+$db_name+"\userdefined.rpt"
$presetup = ".\"+$db_name+"\pre.setup.sql"
$presetup_rpt = ".\"+$db_name+"\presetup.rpt"
$tables = ".\"+$db_name+"\tables.sql"
$tables_rpt = ".\"+$db_name+"\tables.rpt"
$procs = ".\"+$db_name+"\procs.sql"
$procs_rpt = ".\"+$db_name+"\procs.rpt"
$triggers = ".\"+$db_name+"\triggers.sql"
$triggers_rpt = ".\"+$db_name+"\triggers.rpt"
Write-Verbose $creation
Write-Verbose $exists
if ($exists -ne $true){
Write-Verbose "Starting creation script..."
Invoke-Sqlcmd -InputFile $creation | Out-File -FilePath $creation_rpt
Write-Verbose "Starting user defined script..."
Invoke-Sqlcmd -InputFile $userdefined | Out-File -FilePath $userdefined_rpt
Write-Verbose "Starting pre.setup script..."
Invoke-Sqlcmd -InputFile $presetup | Out-File -FilePath $presetup_rpt
Write-Verbose "Starting tables script..."
Invoke-Sqlcmd -InputFile $tables | Out-File -FilePath $tables_rpt
Write-Verbose "Starting triggers script..."
Invoke-Sqlcmd -InputFile $triggers | Out-File -FilePath $triggers_rpt
Write-Verbose "Starting procs script..."
Invoke-Sqlcmd -InputFile $procs | Out-File -FilePath $procs_rpt
}
Get-EventLog -LogName Application -Source "MSSQL*" -After (Get-Date).AddSeconds(-2) | Select-Object TimeGenerated, EntryType, Message
I can't share the sql files startsql calls, but 99% of the sql
is SSMS generate scripts from an existing DB that I am replicating. The 1% that isn't generated by SSMS is a command to link the two databases being created.
Volumes
You're spot on, volumes can (And should!) be used to persist your data.
Microsoft themselves have docs on how to persist data from containerised SQL servers, including the required commands:
https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-docker-container-configure?view=sql-server-ver15&pivots=cs1-bash#persist
However, this is for Linux, not Windows, so the paths will be different (Very likely the defaults for non-containerised work)
To find that location, you could probably use a query found below, or hop into the container while it is running (using docker exec) and navigate around:
https://www.netwrix.com/how_to_view_sql_server_database_file_location.html
When using volumes with docker-compose the spec can be found here, and is really simple to follow:
https://docs.docker.com/storage/volumes/#use-a-volume-with-docker-compose
(Edit) Proof of Concept
I played around with the Windows container and managed to get the volumes working fine.
I ditched your Dockerfile, and just used the base container image, see below.
version: "3.9"
services:
db:
image: microsoft/mssql-server-windows-developer
volumes:
- .\db:C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA
ports:
- 1433:1433
environment:
SA_PASSWORD: "Abc12345678"
ACCEPT_EULA: "Y"
This works for me because I specified the MDF file location upon database creation:
/*
https://learn.microsoft.com/en-us/sql/relational-databases/databases/create-a-database?view=sql-server-ver15
*/
USE master ;
GO
CREATE DATABASE Sales
ON
( NAME = Sales_dat,
FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\saledat.mdf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 5 )
LOG ON
( NAME = Sales_log,
FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\salelog.ldf',
SIZE = 5MB,
MAXSIZE = 25MB,
FILEGROWTH = 5MB ) ;
GO
EXEC sp_databases ;
You can see that the filepath in the container there correlates to the volume path in the docker-compose file. When I stopped the container, I could successfully see the mdf file in the .\db folder of my project. If you managed to locate the filepath from running your query, you can simply add that to the volume spec in the same fashion as above.When restarting the container, everything loaded fine, and the SP returned a valid list of all DB's.
Windows Containers
I knew they were regarded as a bad idea, but me oh my, did I not expect the base image to be 15GB.
This is ridiculously large, and depending on your use case, will present issues with the development, and deployment process, simply in terms of the time required to download the image.
If you can use Linux containers for your purposes, I highly recommend it as they are production ready, small, lightweight, and better supported. They can even be ran as the developer edition, and the Microsoft docs clearly state how to persist data from these containers
Linux: https://hub.docker.com/_/microsoft-mssql-server
Windows: https://hub.docker.com/r/microsoft/mssql-server-windows-developer/
Ex:
# Using Windows containers in Powershell
PS> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
microsoft/mssql-server-windows-developer latest 19873f41b375 3 years ago 15.1GB
# Using Linux containers in WSL
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mcr.microsoft.com/mssql/server 2019-latest 56beb1db7406 10 days ago 1.54GB

Capture messages output from SQL Server

I have below PS script which creates a table. I would like to capture messages output (Command(s) completed successfully.) from sql server. Is there a way to achieve that?
I tried -Verbose switch but that didn't helped.
PS File:
$CreateTableFile = "C:\DBScripts\CreateTable.sql"
Invoke-Sqlcmd -ServerInstance xyz -InputFile $CreateTableFile -Database "PSLearning" -Verbose
CreateTable.sql:
CREATE TABLE abc (
column_1 int,
)
You don't say what you want to capture. Everything or just errors.
Errors, you can use try/catch, everything you can use Start-Transcript, or write your own log code, or stuff like print script.
Example:
Invoke-Sqlcmd -Query "update your database set column_name ={expression} where <search_condition>; PRINT 'update successfully';" –Verbose
As documented in the Bulit-In Help files …
# get function / cmdlet details
(Get-Command -Name Invoke-SqlCmd).Parameters
Get-help -Name Invoke-SqlCmd -Full
Get-help -Name Invoke-SqlCmd -Online
Get-help -Name Invoke-SqlCmd -Examples
Get-help -Name Invoke-SqlCmd -Examples
# -------------------------- EXAMPLE 5 --------------------------
C:\PS>Invoke-Sqlcmd -Query "PRINT N'abc'" -Verbose
VERBOSE: abc
# Description
# -----------
# This example uses the PowerShell -Verbose parameter to return the message output of the PRINT command.
... or the online ones.
Invoke-Sqlcmd
You can display SQL Server message output, such as those that result
from the SQL PRINT statement, by specifying the Verbose parameter
Example 5: Run a query and display verbose output
PSet-Location "SQLSERVER:\SQL\MyComputer\MainInstance"
Invoke-SqlCmd -Query "PRINT N'abc'" -Verbose
VERBOSE: abc

Make Powershell Wait for SQL Script/Query to Finish

So I am trying to write a Powershell script that creates a backup of a databases, compresses the backup, and uploads it to an FTP site. Here is a part of my script
Sample Script/Code:
Write-Host "Backup of Database " $databaseName " is starting"
push-location
Invoke-Sqlcmd -Query "--SQL Script that backs up database--" -ServerInstance "$serverName"
pop-location
Write-Host "Backup of Database " + $databaseName " is complete"
#Create a Zipfile of the database
Write-Host "Compressing Database and creating Zip file...."
sz a -t7z "$zipfile" "$file"
Write-Host "Completed Compressing Database and creating Zip file!"
I am wanting to prevent any code after the "Invoke-Sqlcmd......." part from being executed until the SQL script backing up the database is complete because the compression line is failing to find the backup of the database because the backup takes a fairly long time to complete.
I am extremely new to using Powershell and didn't quite understand what a couple of the other possibly related questions I found were offering as a solution as I call my SQL statement a different way.
Possible Related Questions:
Get Powershell to wait for an SQL command to execute
Powershell run SQL job, delay, then run the next one
Are you sure your ...script that backs up the database isnt just throwing an error and the ps continuing?
This seems to indicate that it does in fact wait on that call:
Write-Host "starting"
push-location
Invoke-Sqlcmd -Query "waitfor delay '00:00:15';" -ServerInstance "$serverName"
pop-location
Write-Host "complete"
In any case, you should guard against the file existing, by either aborting if the file does not exist or polling until it does (i'm not 100% on when the .bak file is written to disk).
# abort
if(!(test-path $file)) {
}
# or, poll
while(!(test-path $file)) {
start-sleep -s 10;
}

Verify if SQLServer installation needs reboot

I managed to install SQL server on a clean machine with scripts.
But sometimes the scripts won't work because the machine needs a reboot.
My ask:
1.Is there any way to detect if reboot is required while installing SQLserver
2.If reboot is needed,reboot it automatically
In summary ,When a reboot is needed,the value is logged in Registry at below places..
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager
Before installing ,you can run below powershell script..
#Adapted from https://gist.github.com/altrive/5329377
#Based on <http://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542>
function Test-PendingReboot
{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
try {
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}
if the above function returns true ,you can run below command..
Restart-Computer -ComputerName "Server01", "Server02", "localhost"
Server* stands for some names servers and localhost stands for local computer
References:
http://ilovepowershell.com/2015/09/10/how-to-check-if-a-server-needs-a-reboot/
Or lunch the install from command line (cmd) like so
C:\Users\username\Downloads\SQLEXPR_x64_ENU.exe /SKIPRULES=RebootRequiredCheck /ACTION=Install
fist is the address where the setup exe is located and second the parameters to skip reboot check

check RAM,page file, /PAE, /3GB, SQL server memory using powershell

I am a powershell novice.
After days of searching....
I have put together a small powershell script (as below) to check page file, /PAE switch, /3GB switch, SQL server max RAM, min RAM.
I am running this on 1 server.
If I want to run it on many servers (from a .txt) file, How can I change it ?
How can I change it to search boot.ini file's contents for a given server?
clear
$strComputer="."
$PageFile=Get-WmiObject Win32_PageFile -ComputerName $strComputer
Write-Host "Page File Size in MB: " ($PageFile.Filesize/(1024*1024))
$colItems=Get-WmiObject Win32_PhysicalMemory -Namespace root\CIMv2 -ComputerName $strComputer
$total=0
foreach ($objItem in $colItems)
{
$total=$total+ $objItem.Capacity
}
$isPAEEnabled =Get-WmiObject Win32_OperatingSystem -ComputerName $strComputer
Write-Host "Is PAE Enabled: " $isPAEEnabled.PAEEnabled
Write-Host "Is /3GB Enabled: " | Get-Content C:\boot.ini | Select-String "/3GB" -Quiet
# how can I change to search boot.ini file's contents on $strComputer
$smo = new-object('Microsoft.SqlServer.Management.Smo.Server') $strSQLServer
$memSrv = $smo.information.physicalMemory
$memMin = $smo.configuration.minServerMemory.runValue
$memMax = $smo.configuration.maxServerMemory.runValue
## DBMS
Write-Host "Server RAM available: " -noNewLine
Write-Host "$memSrv MB" -fore "blue"
Write-Host "SQL memory Min: " -noNewLine
Write-Host "$memMin MB "
Write-Host "SQL memory Max: " -noNewLine
Write-Host "$memMax MB"
Any comments how this can be improved?
Thanks in advance
In case you would like just to check the boot.ini file you could use Get-Content (in case you will not have problems with credentials)
# create a test file with server names
#"
server1
server2
"# | set-content c:\temp\serversso.txt
# read the file and get the content
get-content c:\temp\serversso.txt |
% { get-content "\\$_\c`$\boot.ini" } |
Select-String "/3GB" -Quiet
Later if you add some stuff that will be needed to run on remote computer then you will need to use remoting and basically Invoke-Command. Recently two resources appeared that touch remoting:
Administrator's Guid to Windows PowerShell Remoting
Series on PowerShell remoting by Ravikanth Chaganti that starts here

Resources