@@ -62,8 +62,21 @@ parameters:
6262 type : string
6363 default : $(LocalDbSharedInstanceName)
6464
65+ # Step execution order:
66+ # 1. Enable TCP, NP & Firewall — Enable TCP and Named Pipes protocols, open firewall ports
67+ # 2. Create SQL user — Create test login/user, grant sysadmin, enable SA; restarts SQL if needed
68+ # 3. Enable FileStream [Win] — (conditional) Enable FileStream via WMI; sp_configure deferred to step 8
69+ # 4. Create FileStreamFolder — (conditional) Create the FileStream data directory
70+ # 5. Setup SQL Alias — Register TCP aliases in x86/x64 registry paths
71+ # 6. Add SQL Certificate — Generate self-signed cert, trust it, bind to SQL Server, grant key access
72+ # 7. Restart SQL Server — Restart to pick up protocol, cert, and FileStream WMI changes; wait for readiness
73+ # 8. Configure FileStream Access — (conditional) Run sp_configure filestream_access_level after restart
74+ # 9. Start SQL Server Browser — Start the Browser service for named-instance discovery
75+ # 10. Enable LocalDB — (conditional) Start and share the LocalDB instance
76+
6577steps :
6678
79+ # Step 1: Enable TCP, Named Pipes protocols and configure Windows Firewall rules.
6780 # GOTCHA: We must use the Windows-only powershell task here instead of the cross-platform pwsh
6881 # task because we call some Windows-specific cmdlets.
6982 - powershell : |
@@ -108,6 +121,9 @@ steps:
108121 displayName: 'Enable TCP, NP & Firewall [Win]'
109122 retryCountOnTaskFailure: 2
110123
124+ # Step 2: Create test login and user with sysadmin privileges, enable and set the SA password.
125+ # If SQL Server is unresponsive (e.g. after protocol changes), this step will restart it once
126+ # and retry before failing.
111127 - powershell : |
112128 $password = "${{ parameters.saPassword }}"
113129
@@ -119,11 +135,19 @@ steps:
119135
120136 Write-Host $machineName
121137 Import-Module "sqlps"
138+
139+ # Determine the Windows service name for this SQL instance.
140+ $serviceName = "${{parameters.instanceName }}"
141+ if ("${{parameters.instanceName }}" -ne "MSSQLSERVER") {
142+ $serviceName = "MSSQL`$${{parameters.instanceName }}"
143+ }
144+
145+ $hasRestarted = $false
122146 $tries = 0
123147 while ($true) {
124148 $tries++
125149 try {
126- Invoke-Sqlcmd -ServerInstance "$machineName" @"
150+ Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @"
127151 CREATE LOGIN [${{parameters.user }}] WITH PASSWORD=N'$password',
128152 DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF;
129153 CREATE USER [${{parameters.user }}] FROM LOGIN [${{parameters.user }}];
@@ -133,11 +157,20 @@ steps:
133157 "@
134158 break
135159 } catch {
136- if ($tries -ge 5) {
137- Write-Host "##[error]Failed to create database user after $tries tries."
138- break
160+ if (-not $hasRestarted -and $tries -ge 5) {
161+ # SQL Server may need a restart after protocol changes (TCP/NP).
162+ Write-Host "Connection failed after $tries attempts. Restarting SQL Server ($serviceName) and retrying..."
163+ Restart-Service -Name $serviceName -Force -ErrorAction Stop
164+ $hasRestarted = $true
165+ $tries = 0
166+ Start-Sleep -Seconds 5
167+ continue
168+ }
169+ if ($tries -ge 10) {
170+ Write-Host "##[error]Failed to create database user after $tries tries (including a restart)."
171+ throw
139172 }
140- Write-Host "Failed to connect to server. Retrying in 5 seconds..."
173+ Write-Host "Failed to connect to server (attempt $tries) . Retrying in 5 seconds..."
141174 Start-Sleep -Seconds 5
142175 }
143176 }
@@ -146,56 +179,30 @@ steps:
146179 SQL_USER: ${{parameters.user }}
147180 SQL_PASSWD: ${{ parameters.saPassword }}
148181
182+ # Step 3: Enable FileStream at the OS/WMI level (conditional on SQLRootPath being set).
183+ # Only the WMI flag is toggled here. The T-SQL sp_configure call happens in step 8,
184+ # after the full SQL Server restart in step 7, to avoid needing a double restart.
149185 - ${{ if ne(parameters.SQLRootPath, '') }} :
150186 - powershell : |
151- #Enable FileStream
187+ #Enable FileStream via WMI.
188+ # The sp_configure call is deferred to after the "Restart SQL Server [Win]"
189+ # step so we don't need a separate restart here.
152190 $instance = "${{parameters.instanceName }}"
153191 $wmi = Get-WmiObject -Namespace "${{parameters.SQLRootPath }}" -Class FilestreamSettings | where {$_.InstanceName -eq $instance}
154192 $wmi.EnableFilestream(3, $instance)
155-
156- $machineName = $env:COMPUTERNAME
157-
158- if ("${{parameters.instanceName }}" -ne "MSSQLSERVER"){
159- $machineName += "\${{parameters.instanceName }}"
160- }
161-
162- #Change the access level for FileStream for SQLServer
163- Set-ExecutionPolicy Unrestricted
164- Import-Module "sqlps"
165-
166- # Retry loop: SQL Server may be temporarily unavailable after enabling FileStream via WMI.
167- # Worst-case budget: 10 attempts x (5s connection timeout + 5s sleep) = ~100s
168- $tries = 0
169- while ($true) {
170- $tries++
171- try {
172- Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @"
173- EXEC sp_configure filestream_access_level, 2;
174- RECONFIGURE;
175- "@
176- Write-Host "FileStream access level configured successfully."
177- break
178- } catch {
179- if ($tries -ge 10) {
180- Write-Host "##[error]Failed to configure FileStream access level after $tries tries."
181- throw
182- }
183- Write-Host "Failed to connect to SQL Server (attempt $tries/10). Retrying in 5 seconds..."
184- Start-Sleep -Seconds 5
185- }
186- }
193+ Write-Host "FileStream enabled via WMI for instance '$instance'."
187194 displayName: 'Enable FileStream [Win]'
188- env:
189- SQL_USER: ${{parameters.user }}
190- SQL_PASSWD: ${{ parameters.saPassword }}
191195
196+ # Step 4: Create the FileStream data directory (conditional on fileStreamDirectory being set).
192197 - ${{ if ne(parameters.FileStreamDirectory, '') }} :
193198 - powershell : |
194199 New-Item -Path ${{ parameters.fileStreamDirectory }} -ItemType Directory
195200 displayName: 'Create FileStreamFolder'
196201 retryCountOnTaskFailure: 1
197202 continueOnError: true
198203
204+ # Step 5: Register TCP-based SQL Server aliases in both x86 and x64 registry hives
205+ # so test connections using the alias name resolve to the correct host and port.
199206 - powershell : |
200207 $SQLServerName = ("{0}" -f [System.Net.Dns]::GetHostByName($env:computerName).HostName)
201208 Write-Host FQDN is: $SQLServerName
@@ -214,6 +221,9 @@ steps:
214221 New-ItemProperty -Path ${{parameters.x64AliasRegistryPath }} -Name ${{parameters.SQLAliasName }} -PropertyType string -Value $TCPAliasName
215222 displayName: 'Setup SQL Alias [Win]'
216223
224+ # Step 6: Generate a self-signed TLS certificate, add it to the trusted root store,
225+ # bind it to all SQL Server instances, and grant the SQL service account read access
226+ # to the private key.
217227 - powershell : |
218228 # Create Certificate
219229 $computerDnsName = [System.Net.Dns]::Resolve($null).HostName
@@ -251,6 +261,9 @@ steps:
251261 }
252262 displayName: 'Add SQL Certificate [Win]'
253263
264+ # Step 7: Restart SQL Server to apply all preceding configuration changes (protocols,
265+ # certificate, FileStream WMI). Waits for SQL Server to accept connections before
266+ # proceeding (up to ~160s).
254267 - powershell : |
255268 # You need to restart SQL Server for the change to persist
256269 # -Force takes care of any dependent services, like SQL Agent.
@@ -295,6 +308,27 @@ steps:
295308
296309 displayName: 'Restart SQL Server [Win]'
297310
311+ # Step 8: Configure FileStream access level via T-SQL after the restart so we don't
312+ # need a separate restart in the "Enable FileStream [Win]" step.
313+ - ${{ if ne(parameters.SQLRootPath, '') }} :
314+ - powershell : |
315+ $machineName = $env:COMPUTERNAME
316+ if ("${{parameters.instanceName }}" -ne "MSSQLSERVER") {
317+ $machineName += "\${{parameters.instanceName }}"
318+ }
319+
320+ Set-ExecutionPolicy Unrestricted
321+ Import-Module "sqlps"
322+
323+ Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @"
324+ EXEC sp_configure filestream_access_level, 2;
325+ RECONFIGURE;
326+ "@
327+ Write-Host "FileStream access level configured successfully."
328+ displayName: 'Configure FileStream Access Level [Win]'
329+
330+ # Step 9: Start the SQL Server Browser service so that named instances can be
331+ # discovered by clients.
298332 - powershell : |
299333 $arrService = Get-Service -Name "SQLBrowser"
300334 $arrService
@@ -314,6 +348,7 @@ steps:
314348 }
315349 displayName: 'Start Sql Server Browser [Win]'
316350
351+ # Step 10: Start and share the LocalDB instance (conditional on enableLocalDB).
317352 - ${{ if parameters.enableLocalDB }} :
318353 - powershell : |
319354 #script to enable local db
0 commit comments