A PowerShell script to change the SQL authentication mode and a DevTest Labs artifact built upon it.
PowerShell script
We’ll start with the basic script.
This script uses the SQL Server Management Objects. SMO is installed with SQL Server Management
Studio (SSMS) but it can also be installed via the PowerShell SqlServer module. If you’re
using an Azure VM with a SQL Server base image, SMO is already available.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[ValidateSet('Integrated','Mixed','Normal')]
[string] $AuthMode,
[ValidateNotNullOrEmpty()]
[string] $Server = 'localhost'
)
# Connect to the local SQL instance using SMO
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null
$s = new-object('Microsoft.SqlServer.Management.Smo.Server') $Server
[string]$nm = $s.Name
[string]$mode = $s.Settings.LoginMode
# Change to Mixed Mode
$s.Settings.LoginMode = [Microsoft.SqlServer.Management.SMO.ServerLoginMode] $AuthMode
$s.Alter()
# Restart SQL Server to apply changes
Restart-Service -Name MSSQLSERVER
|
Since the Microsoft.SqlServer.SMO assembly may not yet be loaded, using the ServerLoginMode enum
as a parameter type may fail. Hence the use of a string with a ValidateSet. I’ve no idea what
“Normal” means and didn’t bother trying it. PowerShell can convert the string to an enum by
casting.
The Restart-Service call will work with a default install of SQL Server but may need to be set
as a parameter in other instances.
Creating a custom DTL artifact
I need to use this script to change the SQL Server installed in the base VM images to use mixed
mode authentication. So I wrote a custom DTL artifact. Here is the artifact file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
{
"$schema": "https://raw.githubusercontent.com/Azure/azure-devtestlab/master/schemas/2016-11-28/dtlArtifacts.json",
"title": "Change the SQL Server authentication mode",
"description": "Change the SQL Server authentication mode",
"publisher": "Dustin Metzgar",
"tags": [
"SQL Server"
],
"targetOsType": "Windows",
"parameters": {
"AuthMode": {
"type": "string",
"displayName": "Authentication Mode",
"description": "The authentication mode to set the local SQL Server instance to.",
"defaultValue": "Mixed",
"allowedValues": [
"Integrated",
"Mixed"
]
},
"Server": {
"type": "string",
"displayName": "Optional server name",
"description": "Server to apply the authentication mode change to (defaults to localhost).",
"defaultValue": "",
"allowEmpty": true
}
},
"runCommand": {
"commandToExecute": "[concat('powershell.exe -ExecutionPolicy bypass \"& ./SetSqlAuthMode.ps1 ', parameters('AuthMode'), ' ', parameters('Server'), '\"')]"
}
}
|
Since I don’t know what “Normal” does, I dropped it from the list of values in the artifact
parameter. The PowerShell script has the [CmdletBinding]
attribute so the parameters don’t need
the names in front. That means the Server parameter can be blank in the attribute.
How to get the script working in DevTest Labs
If you run the script by itself while logged into an Azure VM, it will work. But using it as-is in
a DevTest Labs artifact will fail. I added the snippet below to get more details on the exception:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# Handle all script errors
trap
{
$exc = $Error[0].Exception
do {
$message = $exc.Message
if ($message)
{
Write-Host -Object "ERROR: $message" -ForegroundColor Red
}
$exc = $exc.InnerException
} while ($exc)
Write-Host "`nThe artifact failed to apply.`n"
exit -1
}
|
This allows me to see the root cause:
ERROR: Exception calling "Alter" with "0" argument(s): "Alter failed for Server 'myvmname'. "
ERROR: Alter failed for Server 'myvmname'.
ERROR: An exception occurred while executing a Transact-SQL statement or batch.
ERROR: The EXECUTE permission was denied on the object 'xp_instance_regwrite', database 'mssqlsystemresource', schema 'sys'.
The artifact failed to apply.
The reason for this has to do with the user account that is applying the artifacts. It’s not an
actual user that you can grant sysadmin privileges to. An artifact has admin rights on the Azure VM
but not admin rights to SQL Server.
Luckily there is a way to get around this. SQL Server has a way to allow an admin on the host
machine access called single-user mode. See this article
for more information on how it works.
The first step is to shut down the database server. Since only a single user can access the server
at a time, any services that could connect to it should be shut down as well.
1
2
3
|
Stop-Service -Name MSSQLFDLauncher
Stop-Service -Name MsDtsServer150
Stop-Service -Name MSSQLSERVER
|
The next step is to start the server with the “-m” option:
1
2
3
|
$sqlJob = Start-Job -Name Sql -ScriptBlock {
& 'C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\Binn\sqlservr.exe' -m
}
|
The SQL Server stays alive as long as the process is running so I need to start it in a separate
job. I hold onto the job object so I can stop it after making the changes. Then I can turn the
services back on.
1
2
3
4
5
|
$sqlJob.StopJob()
Start-Service -Name MSSQLSERVER
Start-Service -Name MsDtsServer150
Start-Service -Name MSSQLFDLauncher
|
The full script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[ValidateSet('Integrated','Mixed','Normal')]
[string] $AuthMode,
[ValidateNotNullOrEmpty()]
[string] $Server = 'localhost'
)
# Handle all script errors
trap
{
$exc = $Error[0].Exception
do {
$message = $exc.Message
if ($message)
{
Write-Host -Object "ERROR: $message" -ForegroundColor Red
}
$exc = $exc.InnerException
} while ($exc)
Write-Host "`nThe artifact failed to apply.`n"
exit -1
}
Stop-Service -Name MSSQLFDLauncher
Stop-Service -Name MsDtsServer150
Stop-Service -Name MSSQLSERVER
$sqlJob = Start-Job -Name Sql -ScriptBlock {
& 'C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\Binn\sqlservr.exe' -m
}
try {
Write-Host 'Sql Server status'
$sqlJob.JobStateInfo
# Connect to the local SQL instance using SMO
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null
$s = new-object('Microsoft.SqlServer.Management.Smo.Server') $Server
[string]$nm = $s.Name
[string]$mode = $s.Settings.LoginMode
# Change to Mixed Mode
$s.Settings.LoginMode = [Microsoft.SqlServer.Management.SMO.ServerLoginMode] $AuthMode
$s.Alter()
}
finally {
$sqlJob.StopJob()
Start-Service -Name MSSQLSERVER
Start-Service -Name MsDtsServer150
Start-Service -Name MSSQLFDLauncher
}
|