Created
June 3, 2026 23:02
-
-
Save sphinxid/9f434cc1ec5118854cc61f4714c8522b to your computer and use it in GitHub Desktop.
A power shell script that will help you identify what kind of Microsoft SQL Server installed on the device.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Detect SQL Server Database Engine instances and SQL services. | |
| .DESCRIPTION | |
| Default: | |
| - Output vertically to screen | |
| - CSV only if -CsvPath is provided | |
| - No SQL component/uninstall detection | |
| - Detects SQL engine from services first: | |
| MSSQLSERVER | |
| MSSQL$INSTANCE | |
| - Then tries to find version/edition from registry. | |
| - If registry is missing and service is running, it tries SERVERPROPERTY() with short timeout. | |
| .EXAMPLE | |
| .\check_sql_version.ps1 | |
| .EXAMPLE | |
| .\check_sql_version.ps1 -CsvPath "C:\Temp\SQL_Detection.csv" | |
| .EXAMPLE | |
| .\check_sql_version.ps1 -SkipSqlConnection | |
| #> | |
| param( | |
| [string]$CsvPath, | |
| [switch]$SkipSqlConnection, | |
| [int]$ConnectionTimeoutSeconds = 3, | |
| [switch]$DebugOutput | |
| ) | |
| $ComputerName = $env:COMPUTERNAME | |
| $Results = @() | |
| function Write-DebugLine { | |
| param([string]$Message) | |
| if ($DebugOutput) { | |
| Write-Output "[DEBUG] $Message" | |
| } | |
| } | |
| function Test-IsBlank { | |
| param([string]$Value) | |
| return [string]::IsNullOrWhiteSpace($Value) | |
| } | |
| function Get-SqlVersionYear { | |
| param([string]$ProductVersion) | |
| if (Test-IsBlank $ProductVersion) { | |
| return "" | |
| } | |
| $major = $ProductVersion.Split(".")[0] | |
| switch ($major) { | |
| "11" { return "SQL Server 2012" } | |
| "12" { return "SQL Server 2014" } | |
| "13" { return "SQL Server 2016" } | |
| "14" { return "SQL Server 2017" } | |
| "15" { return "SQL Server 2019" } | |
| "16" { return "SQL Server 2022" } | |
| "17" { return "SQL Server 2025" } | |
| default { return "Unknown / Other" } | |
| } | |
| } | |
| function Get-RiskClassification { | |
| param( | |
| [string]$Edition, | |
| [bool]$HasSqlEngineService, | |
| [bool]$HasRunningSqlEngineService | |
| ) | |
| if (Test-IsBlank $Edition) { | |
| if ($HasRunningSqlEngineService) { | |
| return "SQL engine running, but edition not found" | |
| } | |
| elseif ($HasSqlEngineService) { | |
| return "SQL engine service found but not running" | |
| } | |
| else { | |
| return "No SQL Database Engine instance found" | |
| } | |
| } | |
| if ($Edition -match "Express") { | |
| return "Low - SQL Express detected" | |
| } | |
| elseif ($Edition -match "Developer") { | |
| return "High if production - Developer Edition" | |
| } | |
| elseif ($Edition -match "Evaluation") { | |
| return "High - Evaluation Edition" | |
| } | |
| elseif ($Edition -match "Standard|Enterprise|Web|Business Intelligence") { | |
| return "Requires license evidence" | |
| } | |
| else { | |
| return "Review" | |
| } | |
| } | |
| function Get-AllSqlServices { | |
| try { | |
| return @(Get-Service *SQL* -ErrorAction SilentlyContinue | Select-Object Name, Status, DisplayName) | |
| } | |
| catch { | |
| Write-DebugLine "Failed to get SQL services: $($_.Exception.Message)" | |
| return @() | |
| } | |
| } | |
| function Get-SqlEngineServices { | |
| param($AllSqlServices) | |
| if ($null -eq $AllSqlServices) { | |
| return @() | |
| } | |
| return @( | |
| $AllSqlServices | | |
| Where-Object { | |
| $_.Name -eq "MSSQLSERVER" -or | |
| $_.Name -like 'MSSQL$*' | |
| } | |
| ) | |
| } | |
| function Convert-ServiceListToText { | |
| param($Services) | |
| if ($null -eq $Services -or @($Services).Count -eq 0) { | |
| return "" | |
| } | |
| $lines = @() | |
| foreach ($svc in @($Services)) { | |
| $lines += "$($svc.Name) [$($svc.Status)] - $($svc.DisplayName)" | |
| } | |
| return ($lines -join "; ") | |
| } | |
| function Get-InstanceNameFromServiceName { | |
| param([string]$ServiceName) | |
| if ($ServiceName -eq "MSSQLSERVER") { | |
| return "MSSQLSERVER" | |
| } | |
| if ($ServiceName -like 'MSSQL$*') { | |
| return $ServiceName.Substring(6) | |
| } | |
| return "" | |
| } | |
| function Get-ServerInstanceName { | |
| param([string]$InstanceName) | |
| if ($InstanceName -eq "MSSQLSERVER") { | |
| return $ComputerName | |
| } | |
| return "$ComputerName\$InstanceName" | |
| } | |
| function Get-RegistryInstanceIdFromInstanceName { | |
| param([string]$InstanceName) | |
| $paths = @( | |
| "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL", | |
| "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SQL Server\Instance Names\SQL" | |
| ) | |
| foreach ($path in $paths) { | |
| if (Test-Path $path) { | |
| try { | |
| $props = Get-ItemProperty -Path $path -ErrorAction Stop | |
| $value = $props.$InstanceName | |
| if (-not (Test-IsBlank $value)) { | |
| return $value | |
| } | |
| } | |
| catch { | |
| Write-DebugLine "Failed reading instance mapping from $path : $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| return "" | |
| } | |
| function Find-SqlSetupInfoByInstanceName { | |
| param([string]$InstanceName) | |
| $basePaths = @( | |
| "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server", | |
| "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SQL Server" | |
| ) | |
| foreach ($basePath in $basePaths) { | |
| if (!(Test-Path $basePath)) { | |
| continue | |
| } | |
| try { | |
| $children = Get-ChildItem -Path $basePath -ErrorAction SilentlyContinue | |
| foreach ($child in $children) { | |
| $instanceId = $child.PSChildName | |
| # Match keys like MSSQL16.SQLEXPRESS, MSSQL15.MSSQLSERVER, etc. | |
| $escapedInstance = [regex]::Escape($InstanceName) | |
| if ($instanceId -notmatch "^MSSQL\d+\.$escapedInstance$") { | |
| continue | |
| } | |
| $setupPath = Join-Path $child.PSPath "Setup" | |
| if (Test-Path $setupPath) { | |
| $setup = Get-ItemProperty -Path $setupPath -ErrorAction SilentlyContinue | |
| if ($null -ne $setup) { | |
| return [PSCustomObject]@{ | |
| InstanceId = $instanceId | |
| RegistrySetupPath = $setupPath | |
| Edition = $setup.Edition | |
| Version = $setup.Version | |
| PatchLevel = $setup.PatchLevel | |
| SQLPath = $setup.SQLPath | |
| SQLDataRoot = $setup.SQLDataRoot | |
| } | |
| } | |
| } | |
| } | |
| } | |
| catch { | |
| Write-DebugLine "Failed enumerating $basePath : $($_.Exception.Message)" | |
| } | |
| } | |
| return [PSCustomObject]@{ | |
| InstanceId = "" | |
| RegistrySetupPath = "" | |
| Edition = "" | |
| Version = "" | |
| PatchLevel = "" | |
| SQLPath = "" | |
| SQLDataRoot = "" | |
| } | |
| } | |
| function Get-SqlSetupInfo { | |
| param( | |
| [string]$InstanceName, | |
| [string]$InstanceId | |
| ) | |
| if (-not (Test-IsBlank $InstanceId)) { | |
| $setupPaths = @( | |
| "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$InstanceId\Setup", | |
| "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SQL Server\$InstanceId\Setup" | |
| ) | |
| foreach ($setupPath in $setupPaths) { | |
| if (Test-Path $setupPath) { | |
| try { | |
| $setup = Get-ItemProperty -Path $setupPath -ErrorAction Stop | |
| return [PSCustomObject]@{ | |
| InstanceId = $InstanceId | |
| RegistrySetupPath = $setupPath | |
| Edition = $setup.Edition | |
| Version = $setup.Version | |
| PatchLevel = $setup.PatchLevel | |
| SQLPath = $setup.SQLPath | |
| SQLDataRoot = $setup.SQLDataRoot | |
| } | |
| } | |
| catch { | |
| Write-DebugLine "Failed reading setup path $setupPath : $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| } | |
| # Fallback: enumerate SQL Server registry keys and match MSSQLxx.InstanceName | |
| return Find-SqlSetupInfoByInstanceName -InstanceName $InstanceName | |
| } | |
| function Get-SqlInfoByConnection { | |
| param( | |
| [string]$ServerInstance, | |
| [int]$TimeoutSeconds | |
| ) | |
| $query = @" | |
| SELECT | |
| CAST(SERVERPROPERTY('Edition') AS nvarchar(128)) AS Edition, | |
| CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128)) AS ProductVersion, | |
| CAST(SERVERPROPERTY('ProductLevel') AS nvarchar(128)) AS ProductLevel, | |
| CAST(SERVERPROPERTY('ProductUpdateLevel') AS nvarchar(128)) AS ProductUpdateLevel, | |
| CAST(SERVERPROPERTY('ProductUpdateReference') AS nvarchar(128)) AS ProductUpdateReference, | |
| CAST(SERVERPROPERTY('EngineEdition') AS nvarchar(128)) AS EngineEdition; | |
| "@ | |
| try { | |
| $connectionString = "Server=$ServerInstance;Database=master;Integrated Security=True;TrustServerCertificate=True;Connection Timeout=$TimeoutSeconds;" | |
| $connection = New-Object System.Data.SqlClient.SqlConnection | |
| $connection.ConnectionString = $connectionString | |
| $command = $connection.CreateCommand() | |
| $command.CommandText = $query | |
| $command.CommandTimeout = $TimeoutSeconds | |
| $connection.Open() | |
| $reader = $command.ExecuteReader() | |
| $data = $null | |
| while ($reader.Read()) { | |
| $data = [PSCustomObject]@{ | |
| Edition = $reader["Edition"] | |
| ProductVersion = $reader["ProductVersion"] | |
| ProductLevel = $reader["ProductLevel"] | |
| ProductUpdateLevel = $reader["ProductUpdateLevel"] | |
| ProductUpdateReference = $reader["ProductUpdateReference"] | |
| EngineEdition = $reader["EngineEdition"] | |
| ConnectionStatus = "Success" | |
| ConnectionError = "" | |
| } | |
| } | |
| $reader.Close() | |
| $connection.Close() | |
| if ($null -eq $data) { | |
| return [PSCustomObject]@{ | |
| Edition = "" | |
| ProductVersion = "" | |
| ProductLevel = "" | |
| ProductUpdateLevel = "" | |
| ProductUpdateReference = "" | |
| EngineEdition = "" | |
| ConnectionStatus = "NoDataReturned" | |
| ConnectionError = "" | |
| } | |
| } | |
| return $data | |
| } | |
| catch { | |
| return [PSCustomObject]@{ | |
| Edition = "" | |
| ProductVersion = "" | |
| ProductLevel = "" | |
| ProductUpdateLevel = "" | |
| ProductUpdateReference = "" | |
| EngineEdition = "" | |
| ConnectionStatus = "Failed" | |
| ConnectionError = $_.Exception.Message | |
| } | |
| } | |
| } | |
| Write-DebugLine "Starting SQL detection on $ComputerName" | |
| Write-DebugLine "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" | |
| Write-DebugLine "64-bit process: $([Environment]::Is64BitProcess)" | |
| $AllSqlServices = Get-AllSqlServices | |
| $SqlEngineServices = Get-SqlEngineServices -AllSqlServices $AllSqlServices | |
| $AllSqlServicesText = Convert-ServiceListToText -Services $AllSqlServices | |
| $SqlEngineServicesText = Convert-ServiceListToText -Services $SqlEngineServices | |
| $HasSqlService = (@($AllSqlServices).Count -gt 0) | |
| $HasSqlEngineService = (@($SqlEngineServices).Count -gt 0) | |
| $HasRunningSqlEngineService = (@($SqlEngineServices | Where-Object { $_.Status -eq "Running" }).Count -gt 0) | |
| if ($HasSqlEngineService) { | |
| foreach ($engineService in @($SqlEngineServices)) { | |
| $instanceName = Get-InstanceNameFromServiceName -ServiceName $engineService.Name | |
| $serverInstance = Get-ServerInstanceName -InstanceName $instanceName | |
| $instanceId = Get-RegistryInstanceIdFromInstanceName -InstanceName $instanceName | |
| $setupInfo = Get-SqlSetupInfo -InstanceName $instanceName -InstanceId $instanceId | |
| if (Test-IsBlank $instanceId) { | |
| $instanceId = $setupInfo.InstanceId | |
| } | |
| $edition = $setupInfo.Edition | |
| $productVersion = $setupInfo.Version | |
| $patchLevel = $setupInfo.PatchLevel | |
| if (Test-IsBlank $productVersion) { | |
| $productVersion = $patchLevel | |
| } | |
| $connectionStatus = "NotAttempted" | |
| $connectionError = "" | |
| # If registry did not return version/edition, try SQL connection when service is running. | |
| if ( | |
| !$SkipSqlConnection -and | |
| $engineService.Status -eq "Running" -and | |
| ((Test-IsBlank $edition) -or (Test-IsBlank $productVersion)) | |
| ) { | |
| $connectionData = Get-SqlInfoByConnection -ServerInstance $serverInstance -TimeoutSeconds $ConnectionTimeoutSeconds | |
| $connectionStatus = $connectionData.ConnectionStatus | |
| $connectionError = $connectionData.ConnectionError | |
| if (-not (Test-IsBlank $connectionData.Edition)) { | |
| $edition = $connectionData.Edition | |
| } | |
| if (-not (Test-IsBlank $connectionData.ProductVersion)) { | |
| $productVersion = $connectionData.ProductVersion | |
| } | |
| if (Test-IsBlank $patchLevel) { | |
| $patchLevel = $connectionData.ProductVersion | |
| } | |
| } | |
| $sqlVersionYear = Get-SqlVersionYear -ProductVersion $productVersion | |
| $risk = Get-RiskClassification ` | |
| -Edition $edition ` | |
| -HasSqlEngineService $HasSqlEngineService ` | |
| -HasRunningSqlEngineService $HasRunningSqlEngineService | |
| $Results += [PSCustomObject]@{ | |
| DeviceName = $ComputerName | |
| DetectionType = "Database Engine Service" | |
| ServerInstance = $serverInstance | |
| InstanceName = $instanceName | |
| InstanceId = $instanceId | |
| SqlVersionYear = $sqlVersionYear | |
| Edition = $edition | |
| ProductVersion = $productVersion | |
| PatchLevel = $patchLevel | |
| ServiceName = $engineService.Name | |
| ServiceStatus = $engineService.Status.ToString() | |
| HasSqlService = $HasSqlService | |
| HasSqlEngineService = $HasSqlEngineService | |
| HasRunningSqlEngineService = $HasRunningSqlEngineService | |
| AllSqlServices = $AllSqlServicesText | |
| SqlEngineServices = $SqlEngineServicesText | |
| RegistryPath = $setupInfo.RegistrySetupPath | |
| ConnectionStatus = $connectionStatus | |
| ConnectionError = $connectionError | |
| RiskClassification = $risk | |
| CollectionStatus = "Success" | |
| Notes = "SQL Database Engine detected from Windows service." | |
| } | |
| } | |
| } | |
| else { | |
| $notes = "" | |
| if ($HasSqlService) { | |
| $notes = "SQL services found, but no SQL Database Engine service was found. Example: SQLBrowser/SQLWriter only." | |
| } | |
| else { | |
| $notes = "No SQL services found." | |
| } | |
| $risk = Get-RiskClassification ` | |
| -Edition "" ` | |
| -HasSqlEngineService $HasSqlEngineService ` | |
| -HasRunningSqlEngineService $HasRunningSqlEngineService | |
| $Results += [PSCustomObject]@{ | |
| DeviceName = $ComputerName | |
| DetectionType = "No Database Engine Service" | |
| ServerInstance = "" | |
| InstanceName = "" | |
| InstanceId = "" | |
| SqlVersionYear = "" | |
| Edition = "" | |
| ProductVersion = "" | |
| PatchLevel = "" | |
| ServiceName = "" | |
| ServiceStatus = "" | |
| HasSqlService = $HasSqlService | |
| HasSqlEngineService = $HasSqlEngineService | |
| HasRunningSqlEngineService = $HasRunningSqlEngineService | |
| AllSqlServices = $AllSqlServicesText | |
| SqlEngineServices = $SqlEngineServicesText | |
| RegistryPath = "" | |
| ConnectionStatus = "" | |
| ConnectionError = "" | |
| RiskClassification = $risk | |
| CollectionStatus = "NoInstanceFound" | |
| Notes = $notes | |
| } | |
| } | |
| # Output | |
| if (-not (Test-IsBlank $CsvPath)) { | |
| $folder = Split-Path $CsvPath -Parent | |
| if (-not (Test-IsBlank $folder) -and !(Test-Path $folder)) { | |
| New-Item -ItemType Directory -Path $folder -Force | Out-Null | |
| } | |
| $Results | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8 | |
| Write-Output "CSV exported to: $CsvPath" | |
| } | |
| else { | |
| foreach ($item in $Results) { | |
| Write-Output "" | |
| Write-Output "============================================================" | |
| Write-Output "SQL Server Detection Result" | |
| Write-Output "============================================================" | |
| $item | | |
| Select-Object ` | |
| DeviceName, | |
| DetectionType, | |
| ServerInstance, | |
| InstanceName, | |
| InstanceId, | |
| SqlVersionYear, | |
| Edition, | |
| ProductVersion, | |
| PatchLevel, | |
| ServiceName, | |
| ServiceStatus, | |
| HasSqlService, | |
| HasSqlEngineService, | |
| HasRunningSqlEngineService, | |
| AllSqlServices, | |
| SqlEngineServices, | |
| RegistryPath, | |
| ConnectionStatus, | |
| ConnectionError, | |
| RiskClassification, | |
| CollectionStatus, | |
| Notes | | |
| Format-List | | |
| Out-String -Width 4096 | | |
| Write-Output | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment