From d3c3d3492b05d76eba3f8201871a968b24e05dff Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:33:54 +0100 Subject: [PATCH 01/31] Added function to check encryption status Added a -CheckEncryption switch to Get-SQLInstanceDomain --- PowerUpSQL.ps1 | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index c4f4acd..0104037 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16179,6 +16179,8 @@ Function Get-SQLInstanceDomain Domain account to filter for. .PARAMETER CheckMgmt Performs UDP scan of servers with registered MSServerClusterMgmtAPI SPNs to help find additional SQL Server instances. + .PARAMETER CheckEncryption + Tests each instance to determine if encryption is enforced. Useful for identifying NTLM relay targets. .PARAMETER UDPTimeOut Timeout in seconds for UDP scans of management servers. Longer timeout = more accurate. .EXAMPLE @@ -16274,6 +16276,11 @@ Function Get-SQLInstanceDomain HelpMessage = 'Performs UDP scan of servers managing SQL Server clusters.')] [switch]$CheckMgmt, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Tests encryption enforcement (NTLM relay vulnerability check).')] + [switch]$CheckEncryption, + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Preforms a DNS lookup on the instance.')] @@ -16303,6 +16310,11 @@ Function Get-SQLInstanceDomain { $null = $TblSQLServerSpns.Columns.Add('IPAddress') } + + if($CheckEncryption) + { + $null = $TblSQLServerSpns.Columns.Add('EncryptionEnforced') + } # Table for UDP scan results of management servers } @@ -16362,6 +16374,80 @@ Function Get-SQLInstanceDomain } $TableRow += $IPAddress } + + # Check encryption enforcement if requested + if($CheckEncryption) + { + Write-Verbose -Message "Testing encryption on $SpnServerInstance..." + $EncryptionStatus = "Unknown" + + # Determine port + $TestPort = 1433 + if($SpnServerInstance -match ',') + { + $TestPort = $SpnServerInstance.Split(',')[1] + $TestComputer = $SpnServerInstance.Split(',')[0] + } + elseif($SpnServerInstance -match '\\') + { + # Named instance - try UDP scan to get port + $TestComputer = $SpnServerInstance.Split('\\')[0] + $TestInstanceName = $SpnServerInstance.Split('\\')[1] + try { + $UDPResult = Get-SQLInstanceScanUDP -ComputerName $TestComputer -SuppressVerbose | + Where-Object {$_.InstanceName -eq $TestInstanceName} | + Select-Object -First 1 + if($UDPResult -and $UDPResult.TCPPort) { + $TestPort = $UDPResult.TCPPort + } + } catch { + # UDP scan failed, skip encryption test + $EncryptionStatus = "Unknown (Port Resolution Failed)" + } + } + else + { + $TestComputer = $SpnServerInstance + } + + # Test encryption if we have a port + if($TestPort -and $EncryptionStatus -eq "Unknown") + { + try { + # Try connection WITHOUT encryption + $ConnStrNoEnc = "Server=$TestComputer,$TestPort;Connection Timeout=3;Encrypt=False;" + $ConnNoEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrNoEnc) + $NoEncSuccess = $false + try { + $ConnNoEnc.Open() + $NoEncSuccess = $true + $ConnNoEnc.Close() + } catch { + $NoEncError = $_.Exception.Message + } finally { + if($ConnNoEnc.State -eq 'Open') { $ConnNoEnc.Close() } + $ConnNoEnc.Dispose() + } + + # Analyze result + if($NoEncSuccess) { + $EncryptionStatus = "No" + } elseif($NoEncError -match "Login failed|Authentication") { + # Got to auth phase without encryption = not enforced + $EncryptionStatus = "No" + } elseif($NoEncError -match "encrypt|SSL|certificate") { + $EncryptionStatus = "Yes" + } else { + $EncryptionStatus = "Unknown" + } + } catch { + $EncryptionStatus = "Unknown" + } + } + + $TableRow += $EncryptionStatus + Write-Verbose -Message " Encryption Enforced: $EncryptionStatus" + } # Add SQL Server spn to table $null = $TblSQLServerSpns.Rows.Add($TableRow) From 0c5caa2162f3e94f8f6c4d34ff6756e8f754586d Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:41:42 +0100 Subject: [PATCH 02/31] Update PowerUpSQL.ps1 --- PowerUpSQL.ps1 | 149 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 0104037..86fc3c3 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16499,6 +16499,155 @@ Function Get-SQLInstanceDomain } +# ------------------------------------------- +# Function: Get-SQLEncryptionStatus +# ------------------------------------------- +# Author: Community Contribution +# Reference: https://blog.compass-security.com/2023/10/relaying-ntlm-to-mssql/ +Function Get-SQLEncryptionStatus +{ + <# + .SYNOPSIS + Tests whether encryption is enforced on a specific SQL Server instance. + Useful for identifying instances vulnerable to NTLM relay attacks. + + .PARAMETER Instance + SQL Server instance to test (e.g., "Server\Instance", "Server,1433", or "Server"). + + .PARAMETER TimeOut + Connection timeout in seconds. Default is 3. + + .EXAMPLE + PS C:\> Get-SQLEncryptionStatus -Instance "SQLServer1.domain.com" -Verbose + VERBOSE: Testing encryption on SQLServer1.domain.com:1433... + VERBOSE: Encryption NOT enforced - Vulnerable to NTLM relay + + Instance EncryptionEnforced + -------- ------------------ + SQLServer1.domain.com No + + .EXAMPLE + PS C:\> Get-SQLEncryptionStatus -Instance "SQLServer1\SQLEXPRESS" + + Tests a named instance (will attempt UDP scan to resolve port). + + .EXAMPLE + PS C:\> "Server1", "Server2\SQL01", "Server3,14330" | Get-SQLEncryptionStatus + + Test multiple instances via pipeline. + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'SQL Server instance to test.')] + [string]$Instance, + + [Parameter(Mandatory = $false, + HelpMessage = 'Connection timeout in seconds.')] + [int]$TimeOut = 3 + ) + + Begin + { + # Setup data table for results + $TblResults = New-Object -TypeName System.Data.DataTable + $null = $TblResults.Columns.Add('Instance') + $null = $TblResults.Columns.Add('EncryptionEnforced') + } + + Process + { + # Parse instance to get computer and port + $Computer = $Instance + $Port = 1433 + + if($Instance -match '\\') + { + # Named instance + $Computer = $Instance.Split('\')[0] + $InstanceName = $Instance.Split('\')[1] + + # Try UDP scan to get port + Write-Verbose "Resolving port for named instance $Instance..." + try { + $UDPResult = Get-SQLInstanceScanUDP -ComputerName $Computer -SuppressVerbose | + Where-Object {$_.InstanceName -eq $InstanceName} | + Select-Object -First 1 + + if($UDPResult -and $UDPResult.TCPPort) { + $Port = $UDPResult.TCPPort + Write-Verbose "Resolved to port $Port" + } else { + Write-Verbose "Could not resolve port via UDP scan" + $null = $TblResults.Rows.Add($Instance, "Unknown (Port Resolution Failed)") + return + } + } catch { + Write-Verbose "Error resolving port: $_" + $null = $TblResults.Rows.Add($Instance, "Unknown (Port Resolution Failed)") + return + } + } + elseif($Instance -match ',') + { + # Port specified + $Computer = $Instance.Split(',')[0] + $Port = $Instance.Split(',')[1] + } + + Write-Verbose "Testing encryption on ${Computer}:${Port}..." + + # Test connection WITHOUT encryption + $EncryptionStatus = "Unknown" + + try { + $ConnStr = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=False;" + $Conn = New-Object System.Data.SqlClient.SqlConnection($ConnStr) + $Success = $false + + try { + $Conn.Open() + $Success = $true + $Conn.Close() + } catch { + $ErrorMsg = $_.Exception.Message + } finally { + if($Conn.State -eq 'Open') { $Conn.Close() } + $Conn.Dispose() + } + + # Analyze result + if($Success) { + $EncryptionStatus = "No" + Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" + } elseif($ErrorMsg -match "Login failed|Authentication") { + $EncryptionStatus = "No" + Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" + } elseif($ErrorMsg -match "encrypt|SSL|certificate") { + $EncryptionStatus = "Yes" + Write-Verbose "Encryption IS enforced - Protected" + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "Could not determine: $ErrorMsg" + } + } catch { + $EncryptionStatus = "Unknown" + Write-Verbose "Error testing instance: $_" + } + + # Add to results + $null = $TblResults.Rows.Add($Instance, $EncryptionStatus) + } + + End + { + return $TblResults + } +} + + # ------------------------------------------- # Function: Get-SQLInstanceLocal # ------------------------------------------- From d149719ad1c9ca5193d85d41427af9584acf5121 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:42:01 +0100 Subject: [PATCH 03/31] Add Get-SQLEncryptionStatus function to PowerUpSQL --- PowerUpSQL.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerUpSQL.psd1 b/PowerUpSQL.psd1 index 6b6b2bb..007e2e6 100644 --- a/PowerUpSQL.psd1 +++ b/PowerUpSQL.psd1 @@ -39,6 +39,7 @@ 'Get-SQLDomainController', 'Get-SQLDomainExploitableSystem', 'Get-SQLDomainGroupMember', + 'Get-SQLEncryptionStatus', 'Get-SQLFuzzDatabaseName', 'Get-SQLFuzzDomainAccount', 'Get-SQLFuzzObjectName', @@ -119,4 +120,3 @@ ) FileList = 'PowerUpSQL.psm1', 'PowerUpSQL.ps1', 'README.md' } - From 3eea917cac8bb6b99f65a29a4881bd7172b17424 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:45:38 +0100 Subject: [PATCH 04/31] Enhance SQL connection tests for encryption status --- PowerUpSQL.ps1 | 99 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 19 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 86fc3c3..a784389 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16418,6 +16418,7 @@ Function Get-SQLInstanceDomain $ConnStrNoEnc = "Server=$TestComputer,$TestPort;Connection Timeout=3;Encrypt=False;" $ConnNoEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrNoEnc) $NoEncSuccess = $false + $NoEncError = "" try { $ConnNoEnc.Open() $NoEncSuccess = $true @@ -16429,14 +16430,37 @@ Function Get-SQLInstanceDomain $ConnNoEnc.Dispose() } - # Analyze result + # Try connection WITH encryption + $ConnStrEnc = "Server=$TestComputer,$TestPort;Connection Timeout=3;Encrypt=True;TrustServerCertificate=True;" + $ConnEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrEnc) + $WithEncSuccess = $false + $WithEncError = "" + try { + $ConnEnc.Open() + $WithEncSuccess = $true + $ConnEnc.Close() + } catch { + $WithEncError = $_.Exception.Message + } finally { + if($ConnEnc.State -eq 'Open') { $ConnEnc.Close() } + $ConnEnc.Dispose() + } + + # Analyze results if($NoEncSuccess) { + # Non-encrypted worked = not enforced $EncryptionStatus = "No" } elseif($NoEncError -match "Login failed|Authentication") { - # Got to auth phase without encryption = not enforced + # Got to auth without encryption = not enforced $EncryptionStatus = "No" + } elseif($WithEncSuccess -and ($NoEncError -match "timeout|not accessible|not found")) { + # Encrypted works but non-encrypted times out = enforced + $EncryptionStatus = "Yes" } elseif($NoEncError -match "encrypt|SSL|certificate") { $EncryptionStatus = "Yes" + } elseif($NoEncError -match "timeout|not accessible|not found" -and $WithEncError -match "Login failed|Authentication") { + # Non-encrypted times out, encrypted gets to login = enforced + $EncryptionStatus = "Yes" } else { $EncryptionStatus = "Unknown" } @@ -16601,40 +16625,77 @@ Function Get-SQLEncryptionStatus # Test connection WITHOUT encryption $EncryptionStatus = "Unknown" + $NoEncryptSuccess = $false + $NoEncryptError = "" try { $ConnStr = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=False;" $Conn = New-Object System.Data.SqlClient.SqlConnection($ConnStr) - $Success = $false try { $Conn.Open() - $Success = $true + $NoEncryptSuccess = $true $Conn.Close() } catch { - $ErrorMsg = $_.Exception.Message + $NoEncryptError = $_.Exception.Message } finally { if($Conn.State -eq 'Open') { $Conn.Close() } $Conn.Dispose() } + } catch { + $NoEncryptError = $_.Exception.Message + } + + # Test connection WITH encryption + $WithEncryptSuccess = $false + $WithEncryptError = "" + + try { + $ConnStrEnc = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=True;TrustServerCertificate=True;" + $ConnEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrEnc) - # Analyze result - if($Success) { - $EncryptionStatus = "No" - Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" - } elseif($ErrorMsg -match "Login failed|Authentication") { - $EncryptionStatus = "No" - Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" - } elseif($ErrorMsg -match "encrypt|SSL|certificate") { - $EncryptionStatus = "Yes" - Write-Verbose "Encryption IS enforced - Protected" - } else { - $EncryptionStatus = "Unknown" - Write-Verbose "Could not determine: $ErrorMsg" + try { + $ConnEnc.Open() + $WithEncryptSuccess = $true + $ConnEnc.Close() + } catch { + $WithEncryptError = $_.Exception.Message + } finally { + if($ConnEnc.State -eq 'Open') { $ConnEnc.Close() } + $ConnEnc.Dispose() } } catch { + $WithEncryptError = $_.Exception.Message + } + + # Analyze results + if($NoEncryptSuccess) { + # Non-encrypted connection succeeded = encryption NOT enforced + $EncryptionStatus = "No" + Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" + } elseif($NoEncryptError -match "Login failed|Authentication") { + # Got to login phase without encryption = encryption NOT enforced + $EncryptionStatus = "No" + Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" + } elseif($WithEncryptSuccess -and ($NoEncryptError -match "timeout|not accessible|not found")) { + # Encrypted works but non-encrypted times out = encryption IS enforced + $EncryptionStatus = "Yes" + Write-Verbose "Encryption IS enforced - Protected (non-encrypted timed out, encrypted succeeded)" + } elseif($NoEncryptError -match "encrypt|SSL|certificate") { + # Explicit encryption error = encryption IS enforced + $EncryptionStatus = "Yes" + Write-Verbose "Encryption IS enforced - Protected" + } elseif($NoEncryptError -match "timeout|not accessible|not found" -and $WithEncryptError -match "Login failed|Authentication") { + # Non-encrypted times out, encrypted gets to login = encryption IS enforced + $EncryptionStatus = "Yes" + Write-Verbose "Encryption IS enforced - Protected (non-encrypted timed out, encrypted reached auth)" + } elseif($NoEncryptError -match "timeout|not accessible|not found") { + # Both timed out or connection failed + $EncryptionStatus = "Unknown" + Write-Verbose "Could not determine - connection timeout/failure on both tests" + } else { $EncryptionStatus = "Unknown" - Write-Verbose "Error testing instance: $_" + Write-Verbose "Could not determine: $NoEncryptError" } # Add to results From 0e317cc11c6167f4260ee67ad6d2a8ca55bddde7 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:45:55 +0100 Subject: [PATCH 05/31] Update PowerUpSQL.psd1 From 66e8ffca3146720cfcd27aa19bfdc8806e089b10 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:47:51 +0100 Subject: [PATCH 06/31] Enhance encryption testing and result logging --- PowerUpSQL.ps1 | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index a784389..b5d38f4 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16628,6 +16628,7 @@ Function Get-SQLEncryptionStatus $NoEncryptSuccess = $false $NoEncryptError = "" + Write-Verbose " Testing without encryption..." try { $ConnStr = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=False;" $Conn = New-Object System.Data.SqlClient.SqlConnection($ConnStr) @@ -16636,20 +16637,24 @@ Function Get-SQLEncryptionStatus $Conn.Open() $NoEncryptSuccess = $true $Conn.Close() + Write-Verbose " Non-encrypted: SUCCESS" } catch { $NoEncryptError = $_.Exception.Message + Write-Verbose " Non-encrypted: FAILED - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." } finally { if($Conn.State -eq 'Open') { $Conn.Close() } $Conn.Dispose() } } catch { $NoEncryptError = $_.Exception.Message + Write-Verbose " Non-encrypted: ERROR - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." } # Test connection WITH encryption $WithEncryptSuccess = $false $WithEncryptError = "" + Write-Verbose " Testing with encryption..." try { $ConnStrEnc = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=True;TrustServerCertificate=True;" $ConnEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrEnc) @@ -16658,44 +16663,52 @@ Function Get-SQLEncryptionStatus $ConnEnc.Open() $WithEncryptSuccess = $true $ConnEnc.Close() + Write-Verbose " Encrypted: SUCCESS" } catch { $WithEncryptError = $_.Exception.Message + Write-Verbose " Encrypted: FAILED - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." } finally { if($ConnEnc.State -eq 'Open') { $ConnEnc.Close() } $ConnEnc.Dispose() } } catch { $WithEncryptError = $_.Exception.Message + Write-Verbose " Encrypted: ERROR - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." } # Analyze results + Write-Verbose " Analyzing results..." if($NoEncryptSuccess) { # Non-encrypted connection succeeded = encryption NOT enforced $EncryptionStatus = "No" - Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" + Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay" } elseif($NoEncryptError -match "Login failed|Authentication") { # Got to login phase without encryption = encryption NOT enforced $EncryptionStatus = "No" - Write-Verbose "Encryption NOT enforced - Vulnerable to NTLM relay" + Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay (reached auth)" } elseif($WithEncryptSuccess -and ($NoEncryptError -match "timeout|not accessible|not found")) { # Encrypted works but non-encrypted times out = encryption IS enforced $EncryptionStatus = "Yes" - Write-Verbose "Encryption IS enforced - Protected (non-encrypted timed out, encrypted succeeded)" + Write-Verbose "RESULT: Encryption IS enforced - Protected (non-encrypted timed out, encrypted succeeded)" } elseif($NoEncryptError -match "encrypt|SSL|certificate") { # Explicit encryption error = encryption IS enforced $EncryptionStatus = "Yes" - Write-Verbose "Encryption IS enforced - Protected" - } elseif($NoEncryptError -match "timeout|not accessible|not found" -and $WithEncryptError -match "Login failed|Authentication") { + Write-Verbose "RESULT: Encryption IS enforced - Protected (explicit encryption required)" + } elseif($NoEncryptError -match "timeout|not accessible|not found" -and ($WithEncryptError -match "Login failed|Authentication")) { # Non-encrypted times out, encrypted gets to login = encryption IS enforced $EncryptionStatus = "Yes" - Write-Verbose "Encryption IS enforced - Protected (non-encrypted timed out, encrypted reached auth)" - } elseif($NoEncryptError -match "timeout|not accessible|not found") { - # Both timed out or connection failed + Write-Verbose "RESULT: Encryption IS enforced - Protected (non-encrypted timed out, encrypted reached auth)" + } elseif(($NoEncryptError -match "timeout|not accessible|not found") -and (($WithEncryptSuccess) -or ($WithEncryptError -notmatch "timeout|not accessible|not found"))) { + # Non-encrypted times out, but encrypted behaved differently = likely enforced + $EncryptionStatus = "Yes" + Write-Verbose "RESULT: Encryption IS enforced - Protected (non-encrypted timed out, encrypted behaved differently)" + } elseif($NoEncryptError -match "timeout|not accessible|not found" -and $WithEncryptError -match "timeout|not accessible|not found") { + # Both timed out $EncryptionStatus = "Unknown" - Write-Verbose "Could not determine - connection timeout/failure on both tests" + Write-Verbose "RESULT: Could not determine - both connection attempts timed out" } else { $EncryptionStatus = "Unknown" - Write-Verbose "Could not determine: $NoEncryptError" + Write-Verbose "RESULT: Could not determine - unexpected error pattern" } # Add to results From 8dc4bd286254b085a906678e2216fb8edd244de5 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:48:04 +0100 Subject: [PATCH 07/31] Update PowerUpSQL.psd1 From 36d516afdabce67cfbb868e2ad870e18ebf8762a Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:53:26 +0100 Subject: [PATCH 08/31] Refactor encryption testing using TDS pre-login --- PowerUpSQL.ps1 | 331 ++++++++++++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 126 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index b5d38f4..4b29d13 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16414,58 +16414,87 @@ Function Get-SQLInstanceDomain if($TestPort -and $EncryptionStatus -eq "Unknown") { try { - # Try connection WITHOUT encryption - $ConnStrNoEnc = "Server=$TestComputer,$TestPort;Connection Timeout=3;Encrypt=False;" - $ConnNoEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrNoEnc) - $NoEncSuccess = $false - $NoEncError = "" - try { - $ConnNoEnc.Open() - $NoEncSuccess = $true - $ConnNoEnc.Close() - } catch { - $NoEncError = $_.Exception.Message - } finally { - if($ConnNoEnc.State -eq 'Open') { $ConnNoEnc.Close() } - $ConnNoEnc.Dispose() - } - - # Try connection WITH encryption - $ConnStrEnc = "Server=$TestComputer,$TestPort;Connection Timeout=3;Encrypt=True;TrustServerCertificate=True;" - $ConnEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrEnc) - $WithEncSuccess = $false - $WithEncError = "" - try { - $ConnEnc.Open() - $WithEncSuccess = $true - $ConnEnc.Close() - } catch { - $WithEncError = $_.Exception.Message - } finally { - if($ConnEnc.State -eq 'Open') { $ConnEnc.Close() } - $ConnEnc.Dispose() - } + # Use TDS pre-login to test encryption enforcement + $TcpClient = New-Object System.Net.Sockets.TcpClient + $ConnectTask = $TcpClient.ConnectAsync($TestComputer, $TestPort) - # Analyze results - if($NoEncSuccess) { - # Non-encrypted worked = not enforced - $EncryptionStatus = "No" - } elseif($NoEncError -match "Login failed|Authentication") { - # Got to auth without encryption = not enforced - $EncryptionStatus = "No" - } elseif($WithEncSuccess -and ($NoEncError -match "timeout|not accessible|not found")) { - # Encrypted works but non-encrypted times out = enforced - $EncryptionStatus = "Yes" - } elseif($NoEncError -match "encrypt|SSL|certificate") { - $EncryptionStatus = "Yes" - } elseif($NoEncError -match "timeout|not accessible|not found" -and $WithEncError -match "Login failed|Authentication") { - # Non-encrypted times out, encrypted gets to login = enforced - $EncryptionStatus = "Yes" + if($ConnectTask.Wait(3000)) { + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = 3000 + $Stream.WriteTimeout = 3000 + + # TDS pre-login packet + $PreLoginPayload = @( + 0x00, 0x00, 0x1A, 0x00, 0x06, + 0x01, 0x00, 0x20, 0x00, 0x01, + 0x02, 0x00, 0x21, 0x00, 0x01, + 0x03, 0x00, 0x22, 0x00, 0x04, + 0xFF, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x00, 0x00, 0x00, 0x00 + ) + + $PayloadLength = $PreLoginPayload.Length + $TotalLength = 8 + $PayloadLength + + $TdsPacket = @( + 0x12, 0x01, + ([byte]($TotalLength -shr 8)), + ([byte]($TotalLength -band 0xFF)), + 0x00, 0x00, 0x00, 0x00 + ) + $PreLoginPayload + + $Stream.Write($TdsPacket, 0, $TdsPacket.Length) + $Stream.Flush() + + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + + # Find ENCRYPTION option + $i = 0 + $EncryptionOffset = -1 + + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + if($OptionType -eq 0xFF) { break } + if($OptionType -eq 0x01) { + $EncryptionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + break + } + $i += 5 + } + + if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset - 8] + + switch($EncryptionValue) { + 0x00 { $EncryptionStatus = "No" } # ENCRYPT_OFF + 0x01 { $EncryptionStatus = "Yes" } # ENCRYPT_ON + 0x02 { $EncryptionStatus = "No" } # ENCRYPT_NOT_SUP + 0x03 { $EncryptionStatus = "No" } # ENCRYPT_REQ + default { $EncryptionStatus = "Unknown" } + } + } + } + + $Stream.Close() + $TcpClient.Close() } else { $EncryptionStatus = "Unknown" } } catch { $EncryptionStatus = "Unknown" + } finally { + if($TcpClient -and $TcpClient.Connected) { + $TcpClient.Close() + } } } @@ -16621,94 +16650,144 @@ Function Get-SQLEncryptionStatus $Port = $Instance.Split(',')[1] } - Write-Verbose "Testing encryption on ${Computer}:${Port}..." + Write-Verbose "Testing encryption on ${Computer}:${Port} using TDS pre-login..." - # Test connection WITHOUT encryption + # Test using TDS pre-login packet (like mssqlrelay does) $EncryptionStatus = "Unknown" - $NoEncryptSuccess = $false - $NoEncryptError = "" - - Write-Verbose " Testing without encryption..." - try { - $ConnStr = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=False;" - $Conn = New-Object System.Data.SqlClient.SqlConnection($ConnStr) - - try { - $Conn.Open() - $NoEncryptSuccess = $true - $Conn.Close() - Write-Verbose " Non-encrypted: SUCCESS" - } catch { - $NoEncryptError = $_.Exception.Message - Write-Verbose " Non-encrypted: FAILED - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." - } finally { - if($Conn.State -eq 'Open') { $Conn.Close() } - $Conn.Dispose() - } - } catch { - $NoEncryptError = $_.Exception.Message - Write-Verbose " Non-encrypted: ERROR - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." - } - - # Test connection WITH encryption - $WithEncryptSuccess = $false - $WithEncryptError = "" - Write-Verbose " Testing with encryption..." try { - $ConnStrEnc = "Server=$Computer,$Port;Connection Timeout=$TimeOut;Encrypt=True;TrustServerCertificate=True;" - $ConnEnc = New-Object System.Data.SqlClient.SqlConnection($ConnStrEnc) + # Create TCP connection + $TcpClient = New-Object System.Net.Sockets.TcpClient + $ConnectTask = $TcpClient.ConnectAsync($Computer, $Port) + $TimeoutMs = $TimeOut * 1000 - try { - $ConnEnc.Open() - $WithEncryptSuccess = $true - $ConnEnc.Close() - Write-Verbose " Encrypted: SUCCESS" - } catch { - $WithEncryptError = $_.Exception.Message - Write-Verbose " Encrypted: FAILED - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." - } finally { - if($ConnEnc.State -eq 'Open') { $ConnEnc.Close() } - $ConnEnc.Dispose() + if($ConnectTask.Wait($TimeoutMs)) { + Write-Verbose " TCP connection established" + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = $TimeoutMs + $Stream.WriteTimeout = $TimeoutMs + + # Build TDS pre-login packet + # TDS Header: Type=0x12 (Pre-Login), Status=0x01 (End of message), Length, SPID=0x0000, PacketID=0x00, Window=0x00 + # Pre-Login Options: VERSION, ENCRYPTION, INSTOPT, THREADID, MARS, TRACEID, FEDAUTHREQUIRED, TERMINATOR + + # Pre-login payload with ENCRYPTION option + $PreLoginPayload = @( + 0x00, 0x00, 0x1A, 0x00, 0x06, # VERSION option: offset 0x001A, length 6 + 0x01, 0x00, 0x20, 0x00, 0x01, # ENCRYPTION option: offset 0x0020, length 1 (this is what we care about!) + 0x02, 0x00, 0x21, 0x00, 0x01, # INSTOPT option: offset 0x0021, length 1 + 0x03, 0x00, 0x22, 0x00, 0x04, # THREADID option: offset 0x0022, length 4 + 0xFF, # TERMINATOR + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00 # VERSION value (9.0.0.0) + 0x00, # ENCRYPTION value: 0x00 = NOT required (we request no encryption) + 0x00, # INSTOPT value + 0x00, 0x00, 0x00, 0x00 # THREADID value + ) + + $PayloadLength = $PreLoginPayload.Length + $TotalLength = 8 + $PayloadLength + + # TDS Header + $TdsPacket = @( + 0x12, # Type: Pre-Login + 0x01, # Status: End of message + ([byte]($TotalLength -shr 8)), # Length high byte + ([byte]($TotalLength -band 0xFF)), # Length low byte + 0x00, 0x00, # SPID + 0x00, # PacketID + 0x00 # Window + ) + $PreLoginPayload + + # Send pre-login packet + Write-Verbose " Sending TDS pre-login packet..." + $Stream.Write($TdsPacket, 0, $TdsPacket.Length) + $Stream.Flush() + + # Read response + Write-Verbose " Reading TDS pre-login response..." + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + + # Parse pre-login response to find ENCRYPTION option + Write-Verbose " Parsing encryption flag from response..." + $i = 0 + $EncryptionOffset = -1 + + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + + if($OptionType -eq 0xFF) { + # Terminator found + break + } + + if($OptionType -eq 0x01) { + # ENCRYPTION option found + $EncryptionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + break + } + + $i += 5 # Move to next option (type + offset[2] + length[2]) + } + + if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset - 8] # Adjust for header + + Write-Verbose " Encryption flag value: 0x$($EncryptionValue.ToString('X2'))" + + switch($EncryptionValue) { + 0x00 { + # ENCRYPT_OFF - Encryption available but not required + $EncryptionStatus = "No" + Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay" + } + 0x01 { + # ENCRYPT_ON - Encryption required + $EncryptionStatus = "Yes" + Write-Verbose "RESULT: Encryption IS enforced - Protected" + } + 0x02 { + # ENCRYPT_NOT_SUP - Encryption not supported + $EncryptionStatus = "No" + Write-Verbose "RESULT: Encryption not supported - Vulnerable to NTLM relay" + } + 0x03 { + # ENCRYPT_REQ - Client requested encryption + $EncryptionStatus = "No" + Write-Verbose "RESULT: Encryption NOT enforced (only client-requested) - Vulnerable to NTLM relay" + } + default { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Unknown encryption value: 0x$($EncryptionValue.ToString('X2'))" + } + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Could not find encryption option in response" + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Invalid response from server" + } + + $Stream.Close() + $TcpClient.Close() + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Connection timeout - server not reachable" } } catch { - $WithEncryptError = $_.Exception.Message - Write-Verbose " Encrypted: ERROR - $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))..." - } - - # Analyze results - Write-Verbose " Analyzing results..." - if($NoEncryptSuccess) { - # Non-encrypted connection succeeded = encryption NOT enforced - $EncryptionStatus = "No" - Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay" - } elseif($NoEncryptError -match "Login failed|Authentication") { - # Got to login phase without encryption = encryption NOT enforced - $EncryptionStatus = "No" - Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay (reached auth)" - } elseif($WithEncryptSuccess -and ($NoEncryptError -match "timeout|not accessible|not found")) { - # Encrypted works but non-encrypted times out = encryption IS enforced - $EncryptionStatus = "Yes" - Write-Verbose "RESULT: Encryption IS enforced - Protected (non-encrypted timed out, encrypted succeeded)" - } elseif($NoEncryptError -match "encrypt|SSL|certificate") { - # Explicit encryption error = encryption IS enforced - $EncryptionStatus = "Yes" - Write-Verbose "RESULT: Encryption IS enforced - Protected (explicit encryption required)" - } elseif($NoEncryptError -match "timeout|not accessible|not found" -and ($WithEncryptError -match "Login failed|Authentication")) { - # Non-encrypted times out, encrypted gets to login = encryption IS enforced - $EncryptionStatus = "Yes" - Write-Verbose "RESULT: Encryption IS enforced - Protected (non-encrypted timed out, encrypted reached auth)" - } elseif(($NoEncryptError -match "timeout|not accessible|not found") -and (($WithEncryptSuccess) -or ($WithEncryptError -notmatch "timeout|not accessible|not found"))) { - # Non-encrypted times out, but encrypted behaved differently = likely enforced - $EncryptionStatus = "Yes" - Write-Verbose "RESULT: Encryption IS enforced - Protected (non-encrypted timed out, encrypted behaved differently)" - } elseif($NoEncryptError -match "timeout|not accessible|not found" -and $WithEncryptError -match "timeout|not accessible|not found") { - # Both timed out $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Could not determine - both connection attempts timed out" - } else { - $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Could not determine - unexpected error pattern" + Write-Verbose "RESULT: Error - $($_.Exception.Message)" + } finally { + if($TcpClient -and $TcpClient.Connected) { + $TcpClient.Close() + } } # Add to results From 1222b50eefed1dde3c5c309bd3d4a9f660a99226 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:53:41 +0100 Subject: [PATCH 09/31] Update PowerUpSQL.psd1 From 0ec3aa4d75210e57c25db78eb08511445f25cd14 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:55:50 +0100 Subject: [PATCH 10/31] Refactor TCP client handling for encryption testing --- PowerUpSQL.ps1 | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 4b29d13..bfa8c2a 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16413,12 +16413,15 @@ Function Get-SQLInstanceDomain # Test encryption if we have a port if($TestPort -and $EncryptionStatus -eq "Unknown") { + $TcpClient = $null try { # Use TDS pre-login to test encryption enforcement $TcpClient = New-Object System.Net.Sockets.TcpClient - $ConnectTask = $TcpClient.ConnectAsync($TestComputer, $TestPort) + $TcpClient.ReceiveTimeout = 3000 + $TcpClient.SendTimeout = 3000 + $TcpClient.Connect($TestComputer, $TestPort) - if($ConnectTask.Wait(3000)) { + if($TcpClient.Connected) { $Stream = $TcpClient.GetStream() $Stream.ReadTimeout = 3000 $Stream.WriteTimeout = 3000 @@ -16492,8 +16495,11 @@ Function Get-SQLInstanceDomain } catch { $EncryptionStatus = "Unknown" } finally { - if($TcpClient -and $TcpClient.Connected) { - $TcpClient.Close() + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } } } } @@ -16654,18 +16660,22 @@ Function Get-SQLEncryptionStatus # Test using TDS pre-login packet (like mssqlrelay does) $EncryptionStatus = "Unknown" + $TcpClient = $null try { # Create TCP connection $TcpClient = New-Object System.Net.Sockets.TcpClient - $ConnectTask = $TcpClient.ConnectAsync($Computer, $Port) - $TimeoutMs = $TimeOut * 1000 + $TcpClient.ReceiveTimeout = $TimeOut * 1000 + $TcpClient.SendTimeout = $TimeOut * 1000 + + Write-Verbose " Connecting to ${Computer}:${Port}..." + $TcpClient.Connect($Computer, $Port) - if($ConnectTask.Wait($TimeoutMs)) { + if($TcpClient.Connected) { Write-Verbose " TCP connection established" $Stream = $TcpClient.GetStream() - $Stream.ReadTimeout = $TimeoutMs - $Stream.WriteTimeout = $TimeoutMs + $Stream.ReadTimeout = $TimeOut * 1000 + $Stream.WriteTimeout = $TimeOut * 1000 # Build TDS pre-login packet # TDS Header: Type=0x12 (Pre-Login), Status=0x01 (End of message), Length, SPID=0x0000, PacketID=0x00, Window=0x00 @@ -16779,14 +16789,17 @@ Function Get-SQLEncryptionStatus $TcpClient.Close() } else { $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Connection timeout - server not reachable" + Write-Verbose "RESULT: Not connected" } } catch { $EncryptionStatus = "Unknown" Write-Verbose "RESULT: Error - $($_.Exception.Message)" } finally { - if($TcpClient -and $TcpClient.Connected) { - $TcpClient.Close() + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } } } From d28309c41c2b55c4b92c148489b9c3e5d285cfd8 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:57:02 +0100 Subject: [PATCH 11/31] Implement timeout for TCP connection attempts --- PowerUpSQL.ps1 | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index bfa8c2a..76f30c6 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16417,9 +16417,22 @@ Function Get-SQLInstanceDomain try { # Use TDS pre-login to test encryption enforcement $TcpClient = New-Object System.Net.Sockets.TcpClient - $TcpClient.ReceiveTimeout = 3000 - $TcpClient.SendTimeout = 3000 - $TcpClient.Connect($TestComputer, $TestPort) + + # Try to connect with timeout + $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne(3000, $false)) { + try { + $TcpClient.EndConnect($ConnectResult) + } catch { + # Connection failed + $EncryptionStatus = "Unknown" + } + } else { + # Timeout + $EncryptionStatus = "Unknown" + } if($TcpClient.Connected) { $Stream = $TcpClient.GetStream() @@ -16665,11 +16678,22 @@ Function Get-SQLEncryptionStatus try { # Create TCP connection $TcpClient = New-Object System.Net.Sockets.TcpClient - $TcpClient.ReceiveTimeout = $TimeOut * 1000 - $TcpClient.SendTimeout = $TimeOut * 1000 Write-Verbose " Connecting to ${Computer}:${Port}..." - $TcpClient.Connect($Computer, $Port) + + # Try to connect with timeout + $ConnectResult = $TcpClient.BeginConnect($Computer, $Port, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne($TimeOut * 1000, $false)) { + try { + $TcpClient.EndConnect($ConnectResult) + } catch { + throw "Connection failed: $($_.Exception.Message)" + } + } else { + throw "Connection timeout" + } if($TcpClient.Connected) { Write-Verbose " TCP connection established" From db46074b2e8549f97fdf5e37355f60b11ba71aab Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:58:02 +0100 Subject: [PATCH 12/31] Improve TCP connection error handling and logging --- PowerUpSQL.ps1 | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 76f30c6..ed4f79f 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16677,22 +16677,38 @@ Function Get-SQLEncryptionStatus try { # Create TCP connection + Write-Verbose " Creating TCP client..." $TcpClient = New-Object System.Net.Sockets.TcpClient - Write-Verbose " Connecting to ${Computer}:${Port}..." + Write-Verbose " Attempting connection to ${Computer}:${Port}..." # Try to connect with timeout - $ConnectResult = $TcpClient.BeginConnect($Computer, $Port, $null, $null) - $WaitHandle = $ConnectResult.AsyncWaitHandle - - if($WaitHandle.WaitOne($TimeOut * 1000, $false)) { - try { - $TcpClient.EndConnect($ConnectResult) - } catch { - throw "Connection failed: $($_.Exception.Message)" + try { + $ConnectResult = $TcpClient.BeginConnect($Computer, $Port, $null, $null) + Write-Verbose " BeginConnect initiated..." + $WaitHandle = $ConnectResult.AsyncWaitHandle + + Write-Verbose " Waiting for connection (timeout: $($TimeOut)s)..." + if($WaitHandle.WaitOne($TimeOut * 1000, $false)) { + Write-Verbose " Connection wait completed, calling EndConnect..." + try { + $TcpClient.EndConnect($ConnectResult) + Write-Verbose " EndConnect successful" + } catch { + Write-Verbose " EndConnect failed: $($_.Exception.GetType().FullName) - $($_.Exception.Message)" + throw "Connection failed: $($_.Exception.Message)" + } + } else { + Write-Verbose " Connection timed out after $($TimeOut) seconds" + throw "Connection timeout" } - } else { - throw "Connection timeout" + } catch { + Write-Verbose " Connection error: $($_.Exception.GetType().FullName)" + Write-Verbose " Error message: $($_.Exception.Message)" + if($_.Exception.InnerException) { + Write-Verbose " Inner exception: $($_.Exception.InnerException.GetType().FullName) - $($_.Exception.InnerException.Message)" + } + throw } if($TcpClient.Connected) { @@ -16817,7 +16833,12 @@ Function Get-SQLEncryptionStatus } } catch { $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Error - $($_.Exception.Message)" + $ErrorDetails = "Type: $($_.Exception.GetType().FullName)`nMessage: $($_.Exception.Message)" + if($_.Exception.InnerException) { + $ErrorDetails += "`nInner: $($_.Exception.InnerException.GetType().FullName) - $($_.Exception.InnerException.Message)" + } + Write-Verbose "RESULT: Error - $ErrorDetails" + Write-Verbose "Full Error:`n$($_ | Out-String)" } finally { if($TcpClient -ne $null) { if($TcpClient.Connected) { From aa673283eb206e6a64653e3f20a3fefaf26f5235 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:58:24 +0100 Subject: [PATCH 13/31] Fix end region comment in PowerUpSQL.ps1 From ff467421a3fb0559af3080c9a762cb7a64bc710d Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:59:05 +0100 Subject: [PATCH 14/31] Fix region end comment in PowerUpSQL.ps1 From e8aed923ae80e1e5dbc052fb54dc560b465d8efa Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:02:38 +0100 Subject: [PATCH 15/31] Enhance error handling and logging in PowerUpSQL --- PowerUpSQL.ps1 | 115 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index ed4f79f..244c8ef 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16695,8 +16695,17 @@ Function Get-SQLEncryptionStatus $TcpClient.EndConnect($ConnectResult) Write-Verbose " EndConnect successful" } catch { - Write-Verbose " EndConnect failed: $($_.Exception.GetType().FullName) - $($_.Exception.Message)" - throw "Connection failed: $($_.Exception.Message)" + $ErrorMsg = $_.Exception.Message + Write-Verbose " EndConnect failed: $($_.Exception.GetType().FullName) - $ErrorMsg" + + # Check for specific errors + if($ErrorMsg -match "actively refused|connection refused") { + throw "Connection refused - SQL Server not listening on port $Port" + } elseif($ErrorMsg -match "timed out") { + throw "Connection timed out" + } else { + throw "Connection failed: $ErrorMsg" + } } } else { Write-Verbose " Connection timed out after $($TimeOut) seconds" @@ -16757,38 +16766,70 @@ Function Get-SQLEncryptionStatus Write-Verbose " Reading TDS pre-login response..." $ResponseHeader = New-Object byte[] 8 $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + Write-Verbose " Read $BytesRead header bytes" if($BytesRead -eq 8) { + $HeaderHex = ($ResponseHeader | ForEach-Object { $_.ToString("X2") }) -join " " + Write-Verbose " Response header: $HeaderHex" + + $ResponseType = $ResponseHeader[0] + $ResponseStatus = $ResponseHeader[1] $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] - $ResponsePayload = New-Object byte[] ($ResponseLength - 8) - $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) - # Parse pre-login response to find ENCRYPTION option - Write-Verbose " Parsing encryption flag from response..." - $i = 0 - $EncryptionOffset = -1 + Write-Verbose " Response type: 0x$($ResponseType.ToString('X2')), status: 0x$($ResponseStatus.ToString('X2')), length: $ResponseLength" - while($i -lt $ResponsePayload.Length) { - $OptionType = $ResponsePayload[$i] + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Read $BytesRead payload bytes (expected $($ResponseLength - 8))" - if($OptionType -eq 0xFF) { - # Terminator found - break - } + $PayloadHex = ($ResponsePayload[0..([Math]::Min(50, $ResponsePayload.Length-1))] | ForEach-Object { $_.ToString("X2") }) -join " " + Write-Verbose " Payload start: $PayloadHex..." - if($OptionType -eq 0x01) { - # ENCRYPTION option found - $EncryptionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] - break - } + # Parse pre-login response to find ENCRYPTION option + Write-Verbose " Parsing encryption flag from response..." + $i = 0 + $EncryptionOffset = -1 - $i += 5 # Move to next option (type + offset[2] + length[2]) - } - - if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { - $EncryptionValue = $ResponsePayload[$EncryptionOffset - 8] # Adjust for header + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + Write-Verbose " Option at index $i : type 0x$($OptionType.ToString('X2'))" + + if($OptionType -eq 0xFF) { + # Terminator found + Write-Verbose " Found terminator at index $i" + break + } + + if($i + 4 -ge $ResponsePayload.Length) { + Write-Verbose " Not enough bytes for option header" + break + } + + $Offset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + $Length = ([int]$ResponsePayload[$i+3] -shl 8) + [int]$ResponsePayload[$i+4] + Write-Verbose " Offset: $Offset, Length: $Length" + + if($OptionType -eq 0x01) { + # ENCRYPTION option found + $EncryptionOffset = $Offset + Write-Verbose " Found ENCRYPTION option! Offset in payload: $EncryptionOffset" + break + } + + $i += 5 # Move to next option (type + offset[2] + length[2]) + } - Write-Verbose " Encryption flag value: 0x$($EncryptionValue.ToString('X2'))" + if($EncryptionOffset -ge 0) { + # The offset is relative to the start of the packet (including header) + # So we need to subtract 8 to get the index in the payload + $PayloadIndex = $EncryptionOffset - 8 + Write-Verbose " Encryption value at payload index: $PayloadIndex" + + if($PayloadIndex -ge 0 -and $PayloadIndex -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$PayloadIndex] + + Write-Verbose " Encryption flag value: 0x$($EncryptionValue.ToString('X2'))" switch($EncryptionValue) { 0x00 { @@ -16818,12 +16859,20 @@ Function Get-SQLEncryptionStatus } } else { $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Could not find encryption option in response" + Write-Verbose "RESULT: Encryption offset out of range (offset: $EncryptionOffset, payload index: $PayloadIndex, payload length: $($ResponsePayload.Length))" } } else { $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Invalid response from server" + Write-Verbose "RESULT: Could not find encryption option in response" } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Invalid response length: $ResponseLength" + } + } else { + $EncryptionStatus = "Unknown" + Write-Verbose "RESULT: Invalid response header (read $BytesRead bytes instead of 8)" + } $Stream.Close() $TcpClient.Close() @@ -16833,12 +16882,14 @@ Function Get-SQLEncryptionStatus } } catch { $EncryptionStatus = "Unknown" - $ErrorDetails = "Type: $($_.Exception.GetType().FullName)`nMessage: $($_.Exception.Message)" - if($_.Exception.InnerException) { - $ErrorDetails += "`nInner: $($_.Exception.InnerException.GetType().FullName) - $($_.Exception.InnerException.Message)" + $ErrorMsg = if($_.Exception.Message -match "Connection refused") { + "Port $Port not accepting connections (wrong port or SQL not running)" + } elseif($_.Exception.Message -match "timed out") { + "Connection timed out" + } else { + $_.Exception.Message } - Write-Verbose "RESULT: Error - $ErrorDetails" - Write-Verbose "Full Error:`n$($_ | Out-String)" + Write-Verbose "RESULT: Cannot test - $ErrorMsg" } finally { if($TcpClient -ne $null) { if($TcpClient.Connected) { From 3ed41e3a4c7209682b9d1313070899528ac42e8f Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:03:15 +0100 Subject: [PATCH 16/31] Fix missing newline at end of PowerUpSQL.ps1 From 0baeec21346b934f554c5da641dc9becaae7234b Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:05:59 +0100 Subject: [PATCH 17/31] Fix formatting and add newline at end of file --- PowerUpSQL.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 244c8ef..1987492 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16737,7 +16737,7 @@ Function Get-SQLEncryptionStatus 0x02, 0x00, 0x21, 0x00, 0x01, # INSTOPT option: offset 0x0021, length 1 0x03, 0x00, 0x22, 0x00, 0x04, # THREADID option: offset 0x0022, length 4 0xFF, # TERMINATOR - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00 # VERSION value (9.0.0.0) + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, # VERSION value (9.0.0.0) 0x00, # ENCRYPTION value: 0x00 = NOT required (we request no encryption) 0x00, # INSTOPT value 0x00, 0x00, 0x00, 0x00 # THREADID value From 8004aefcbb72709537e66a2af6720587d97a89a9 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:07:34 +0100 Subject: [PATCH 18/31] Update Pre-Login Payload offsets in PowerUpSQL.ps1 --- PowerUpSQL.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 1987492..1feda52 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16731,11 +16731,12 @@ Function Get-SQLEncryptionStatus # Pre-Login Options: VERSION, ENCRYPTION, INSTOPT, THREADID, MARS, TRACEID, FEDAUTHREQUIRED, TERMINATOR # Pre-login payload with ENCRYPTION option + # Offsets are from start of packet (header=8 bytes + option table=21 bytes = data starts at byte 29/0x1D) $PreLoginPayload = @( - 0x00, 0x00, 0x1A, 0x00, 0x06, # VERSION option: offset 0x001A, length 6 - 0x01, 0x00, 0x20, 0x00, 0x01, # ENCRYPTION option: offset 0x0020, length 1 (this is what we care about!) - 0x02, 0x00, 0x21, 0x00, 0x01, # INSTOPT option: offset 0x0021, length 1 - 0x03, 0x00, 0x22, 0x00, 0x04, # THREADID option: offset 0x0022, length 4 + 0x00, 0x00, 0x1D, 0x00, 0x06, # VERSION option: offset 0x001D (29), length 6 + 0x01, 0x00, 0x23, 0x00, 0x01, # ENCRYPTION option: offset 0x0023 (35), length 1 (this is what we care about!) + 0x02, 0x00, 0x24, 0x00, 0x01, # INSTOPT option: offset 0x0024 (36), length 1 + 0x03, 0x00, 0x25, 0x00, 0x04, # THREADID option: offset 0x0025 (37), length 4 0xFF, # TERMINATOR 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, # VERSION value (9.0.0.0) 0x00, # ENCRYPTION value: 0x00 = NOT required (we request no encryption) From aa1ebaa6e21c53d670d3794411c08d96020b47f3 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:08:08 +0100 Subject: [PATCH 19/31] Fix end region comment in PowerUpSQL.ps1 From 6d906d213c6366fb24dc640836ac58471ec38669 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:21:14 +0100 Subject: [PATCH 20/31] Refactor encryption handling in PowerUpSQL.ps1 --- PowerUpSQL.ps1 | 145 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 95 insertions(+), 50 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 1feda52..8163c0f 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16490,12 +16490,11 @@ Function Get-SQLInstanceDomain if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { $EncryptionValue = $ResponsePayload[$EncryptionOffset - 8] - switch($EncryptionValue) { - 0x00 { $EncryptionStatus = "No" } # ENCRYPT_OFF - 0x01 { $EncryptionStatus = "Yes" } # ENCRYPT_ON - 0x02 { $EncryptionStatus = "No" } # ENCRYPT_NOT_SUP - 0x03 { $EncryptionStatus = "No" } # ENCRYPT_REQ - default { $EncryptionStatus = "Unknown" } + # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced + if($EncryptionValue -eq 0x02) { + $EncryptionStatus = "No" + } else { + $EncryptionStatus = "Yes" } } } @@ -16726,29 +16725,65 @@ Function Get-SQLEncryptionStatus $Stream.ReadTimeout = $TimeOut * 1000 $Stream.WriteTimeout = $TimeOut * 1000 - # Build TDS pre-login packet + # Build TDS pre-login packet (exactly as impacket does) # TDS Header: Type=0x12 (Pre-Login), Status=0x01 (End of message), Length, SPID=0x0000, PacketID=0x00, Window=0x00 - # Pre-Login Options: VERSION, ENCRYPTION, INSTOPT, THREADID, MARS, TRACEID, FEDAUTHREQUIRED, TERMINATOR - # Pre-login payload with ENCRYPTION option - # Offsets are from start of packet (header=8 bytes + option table=21 bytes = data starts at byte 29/0x1D) - $PreLoginPayload = @( - 0x00, 0x00, 0x1D, 0x00, 0x06, # VERSION option: offset 0x001D (29), length 6 - 0x01, 0x00, 0x23, 0x00, 0x01, # ENCRYPTION option: offset 0x0023 (35), length 1 (this is what we care about!) - 0x02, 0x00, 0x24, 0x00, 0x01, # INSTOPT option: offset 0x0024 (36), length 1 - 0x03, 0x00, 0x25, 0x00, 0x04, # THREADID option: offset 0x0025 (37), length 4 - 0xFF, # TERMINATOR - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, # VERSION value (9.0.0.0) - 0x00, # ENCRYPTION value: 0x00 = NOT required (we request no encryption) - 0x00, # INSTOPT value - 0x00, 0x00, 0x00, 0x00 # THREADID value + # Pre-login option tokens (21 bytes total before data section) + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) # Version 8.0.0.0 + $EncryptionValue = @(0x02) # TDS_ENCRYPT_NOT_SUP - we say we don't support, server tells us what IT requires + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + # Calculate offsets (start from byte 21 after TDS header, which is byte 29 from start of packet) + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + # Build option token section (big-endian for offsets/lengths) + $PreLoginOptions = @( + 0x00, # VERSION token + ([byte]($VersionOffset -shr 8)), # VERSION offset high byte + ([byte]($VersionOffset -band 0xFF)), # VERSION offset low byte + ([byte]($Version.Length -shr 8)), # VERSION length high byte + ([byte]($Version.Length -band 0xFF)), # VERSION length low byte + + 0x01, # ENCRYPTION token + ([byte]($EncryptionOffset -shr 8)), # ENCRYPTION offset high byte + ([byte]($EncryptionOffset -band 0xFF)), # ENCRYPTION offset low byte + 0x00, 0x01, # ENCRYPTION length = 1 + + 0x02, # INSTANCE token + ([byte]($InstanceOffset -shr 8)), # INSTANCE offset high byte + ([byte]($InstanceOffset -band 0xFF)), # INSTANCE offset low byte + ([byte]($InstanceBytes.Length -shr 8)), # INSTANCE length high byte + ([byte]($InstanceBytes.Length -band 0xFF)), # INSTANCE length low byte + + 0x03, # THREADID token + ([byte]($ThreadIDOffset -shr 8)), # THREADID offset high byte + ([byte]($ThreadIDOffset -band 0xFF)), # THREADID offset low byte + 0x00, 0x04, # THREADID length = 4 + + 0xFF # TERMINATOR ) - $PayloadLength = $PreLoginPayload.Length + # Build data section + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + # Build complete payload + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count $TotalLength = 8 + $PayloadLength # TDS Header - $TdsPacket = @( + $TdsHeader = @( 0x12, # Type: Pre-Login 0x01, # Status: End of message ([byte]($TotalLength -shr 8)), # Length high byte @@ -16756,15 +16791,38 @@ Function Get-SQLEncryptionStatus 0x00, 0x00, # SPID 0x00, # PacketID 0x00 # Window - ) + $PreLoginPayload + ) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) # Send pre-login packet Write-Verbose " Sending TDS pre-login packet..." - $Stream.Write($TdsPacket, 0, $TdsPacket.Length) - $Stream.Flush() + try { + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $PacketHex = ($PacketBytes | ForEach-Object { $_.ToString("X2") }) -join " " + Write-Verbose " Packet ($($PacketBytes.Length) bytes): $PacketHex" + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + } catch { + Write-Verbose " Error building/sending packet: $_" + throw + } + + # Give server time to respond + Start-Sleep -Milliseconds 1000 + + # Check if connection is still alive + Write-Verbose " Connection still alive: $($TcpClient.Connected)" # Read response Write-Verbose " Reading TDS pre-login response..." + if($Stream.DataAvailable) { + Write-Verbose " Data is available to read" + } else { + Write-Verbose " WARNING: No data available yet, attempting read anyway..." + } $ResponseHeader = New-Object byte[] 8 $BytesRead = $Stream.Read($ResponseHeader, 0, 8) Write-Verbose " Read $BytesRead header bytes" @@ -16832,31 +16890,18 @@ Function Get-SQLEncryptionStatus Write-Verbose " Encryption flag value: 0x$($EncryptionValue.ToString('X2'))" - switch($EncryptionValue) { - 0x00 { - # ENCRYPT_OFF - Encryption available but not required - $EncryptionStatus = "No" - Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay" - } - 0x01 { - # ENCRYPT_ON - Encryption required - $EncryptionStatus = "Yes" - Write-Verbose "RESULT: Encryption IS enforced - Protected" - } - 0x02 { - # ENCRYPT_NOT_SUP - Encryption not supported - $EncryptionStatus = "No" - Write-Verbose "RESULT: Encryption not supported - Vulnerable to NTLM relay" - } - 0x03 { - # ENCRYPT_REQ - Client requested encryption - $EncryptionStatus = "No" - Write-Verbose "RESULT: Encryption NOT enforced (only client-requested) - Vulnerable to NTLM relay" - } - default { - $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Unknown encryption value: 0x$($EncryptionValue.ToString('X2'))" - } + # When we send TDS_ENCRYPT_NOT_SUP (0x02), server response interpretation: + # 0x02 = Server agrees, no encryption needed = NOT enforced (vulnerable to relay) + # Anything else = Server requires encryption = IS enforced (protected) + if($EncryptionValue -eq 0x02) { + # TDS_ENCRYPT_NOT_SUP - Server allows unencrypted connections + $EncryptionStatus = "No" + Write-Verbose "RESULT: Encryption NOT enforced - Vulnerable to NTLM relay" + } else { + # TDS_ENCRYPT_OFF (0x00), TDS_ENCRYPT_ON (0x01), TDS_ENCRYPT_REQ (0x03) + # All mean encryption is enforced when we say we don't support it + $EncryptionStatus = "Yes" + Write-Verbose "RESULT: Encryption IS enforced (response: 0x$($EncryptionValue.ToString('X2'))) - Protected from NTLM relay" } } else { $EncryptionStatus = "Unknown" From bb445acce911566a30777dd60a4307496ec07397 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:33:46 +0100 Subject: [PATCH 21/31] Increase connection and read timeouts in PowerUpSQL --- PowerUpSQL.ps1 | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 8163c0f..a72a43e 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16418,11 +16418,11 @@ Function Get-SQLInstanceDomain # Use TDS pre-login to test encryption enforcement $TcpClient = New-Object System.Net.Sockets.TcpClient - # Try to connect with timeout + # Try to connect with timeout (10 seconds) $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) $WaitHandle = $ConnectResult.AsyncWaitHandle - if($WaitHandle.WaitOne(3000, $false)) { + if($WaitHandle.WaitOne(10000, $false)) { try { $TcpClient.EndConnect($ConnectResult) } catch { @@ -16436,8 +16436,8 @@ Function Get-SQLInstanceDomain if($TcpClient.Connected) { $Stream = $TcpClient.GetStream() - $Stream.ReadTimeout = 3000 - $Stream.WriteTimeout = 3000 + $Stream.ReadTimeout = 10000 + $Stream.WriteTimeout = 5000 # TDS pre-login packet $PreLoginPayload = @( @@ -16488,7 +16488,7 @@ Function Get-SQLInstanceDomain } if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { - $EncryptionValue = $ResponsePayload[$EncryptionOffset - 8] + $EncryptionValue = $ResponsePayload[$EncryptionOffset] # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced if($EncryptionValue -eq 0x02) { @@ -16586,7 +16586,7 @@ Function Get-SQLEncryptionStatus SQL Server instance to test (e.g., "Server\Instance", "Server,1433", or "Server"). .PARAMETER TimeOut - Connection timeout in seconds. Default is 3. + Connection timeout in seconds. Default is 10. .EXAMPLE PS C:\> Get-SQLEncryptionStatus -Instance "SQLServer1.domain.com" -Verbose @@ -16617,7 +16617,7 @@ Function Get-SQLEncryptionStatus [Parameter(Mandatory = $false, HelpMessage = 'Connection timeout in seconds.')] - [int]$TimeOut = 3 + [int]$TimeOut = 10 ) Begin @@ -16722,8 +16722,9 @@ Function Get-SQLEncryptionStatus if($TcpClient.Connected) { Write-Verbose " TCP connection established" $Stream = $TcpClient.GetStream() + # Set timeouts in milliseconds (convert from seconds) $Stream.ReadTimeout = $TimeOut * 1000 - $Stream.WriteTimeout = $TimeOut * 1000 + $Stream.WriteTimeout = 1000 # Write timeout can be short # Build TDS pre-login packet (exactly as impacket does) # TDS Header: Type=0x12 (Pre-Login), Status=0x01 (End of message), Length, SPID=0x0000, PacketID=0x00, Window=0x00 @@ -16810,19 +16811,8 @@ Function Get-SQLEncryptionStatus throw } - # Give server time to respond - Start-Sleep -Milliseconds 1000 - - # Check if connection is still alive - Write-Verbose " Connection still alive: $($TcpClient.Connected)" - - # Read response + # Read response (Stream.Read is blocking and will wait for data up to ReadTimeout) Write-Verbose " Reading TDS pre-login response..." - if($Stream.DataAvailable) { - Write-Verbose " Data is available to read" - } else { - Write-Verbose " WARNING: No data available yet, attempting read anyway..." - } $ResponseHeader = New-Object byte[] 8 $BytesRead = $Stream.Read($ResponseHeader, 0, 8) Write-Verbose " Read $BytesRead header bytes" @@ -16880,13 +16870,12 @@ Function Get-SQLEncryptionStatus } if($EncryptionOffset -ge 0) { - # The offset is relative to the start of the packet (including header) - # So we need to subtract 8 to get the index in the payload - $PayloadIndex = $EncryptionOffset - 8 - Write-Verbose " Encryption value at payload index: $PayloadIndex" + # The offset is relative to the start of the PAYLOAD (not the packet) + # So we use it directly as an index + Write-Verbose " Encryption value at payload offset: $EncryptionOffset" - if($PayloadIndex -ge 0 -and $PayloadIndex -lt $ResponsePayload.Length) { - $EncryptionValue = $ResponsePayload[$PayloadIndex] + if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset] Write-Verbose " Encryption flag value: 0x$($EncryptionValue.ToString('X2'))" @@ -16905,7 +16894,7 @@ Function Get-SQLEncryptionStatus } } else { $EncryptionStatus = "Unknown" - Write-Verbose "RESULT: Encryption offset out of range (offset: $EncryptionOffset, payload index: $PayloadIndex, payload length: $($ResponsePayload.Length))" + Write-Verbose "RESULT: Encryption offset out of range (offset: $EncryptionOffset, payload length: $($ResponsePayload.Length))" } } else { $EncryptionStatus = "Unknown" From 565d2561df34675d9b73929a1f68f59acf5e61f7 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:34:01 +0100 Subject: [PATCH 22/31] Update PowerUpSQL.psd1 From 196053e08e02b23641ff0aafcbde9982d6ae88d3 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:37:12 +0100 Subject: [PATCH 23/31] Refactor TDS pre-login packet construction --- PowerUpSQL.ps1 | 63 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index a72a43e..39655f6 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16439,30 +16439,65 @@ Function Get-SQLInstanceDomain $Stream.ReadTimeout = 10000 $Stream.WriteTimeout = 5000 - # TDS pre-login packet - $PreLoginPayload = @( - 0x00, 0x00, 0x1A, 0x00, 0x06, - 0x01, 0x00, 0x20, 0x00, 0x01, - 0x02, 0x00, 0x21, 0x00, 0x01, - 0x03, 0x00, 0x22, 0x00, 0x04, - 0xFF, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, + # Build TDS pre-login packet (same as Get-SQLEncryptionStatus) + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) + $EncryptionValue = @(0x02) + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + $PreLoginOptions = @( 0x00, - 0x00, 0x00, 0x00, 0x00 + ([byte]($VersionOffset -shr 8)), + ([byte]($VersionOffset -band 0xFF)), + ([byte]($Version.Length -shr 8)), + ([byte]($Version.Length -band 0xFF)), + 0x01, + ([byte]($EncryptionOffset -shr 8)), + ([byte]($EncryptionOffset -band 0xFF)), + 0x00, 0x01, + 0x02, + ([byte]($InstanceOffset -shr 8)), + ([byte]($InstanceOffset -band 0xFF)), + ([byte]($InstanceBytes.Length -shr 8)), + ([byte]($InstanceBytes.Length -band 0xFF)), + 0x03, + ([byte]($ThreadIDOffset -shr 8)), + ([byte]($ThreadIDOffset -band 0xFF)), + 0x00, 0x04, + 0xFF ) - $PayloadLength = $PreLoginPayload.Length + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count $TotalLength = 8 + $PayloadLength - $TdsPacket = @( + $TdsHeader = @( 0x12, 0x01, ([byte]($TotalLength -shr 8)), ([byte]($TotalLength -band 0xFF)), 0x00, 0x00, 0x00, 0x00 - ) + $PreLoginPayload + ) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) - $Stream.Write($TdsPacket, 0, $TdsPacket.Length) + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) $Stream.Flush() $ResponseHeader = New-Object byte[] 8 From d9a8c04ae8e4826dafbffd1bcc3bbffdac901831 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:38:46 +0100 Subject: [PATCH 24/31] Fix missing newline at end of PowerUpSQL.ps1 From 93098c59be26754ba016ba712068d22ecd78ba0b Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:40:12 +0100 Subject: [PATCH 25/31] Refactor encryption status handling in PowerUpSQL.ps1 --- PowerUpSQL.ps1 | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 39655f6..91865c6 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16502,11 +16502,16 @@ Function Get-SQLInstanceDomain $ResponseHeader = New-Object byte[] 8 $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + Write-Verbose " Read $BytesRead response header bytes" if($BytesRead -eq 8) { $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] - $ResponsePayload = New-Object byte[] ($ResponseLength - 8) - $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Response length: $ResponseLength" + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Read $BytesRead payload bytes" # Find ENCRYPTION option $i = 0 @@ -16522,25 +16527,38 @@ Function Get-SQLInstanceDomain $i += 5 } - if($EncryptionOffset -ge 0 -and $EncryptionOffset -lt $ResponsePayload.Length) { - $EncryptionValue = $ResponsePayload[$EncryptionOffset] - - # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced - if($EncryptionValue -eq 0x02) { - $EncryptionStatus = "No" + if($EncryptionOffset -ge 0) { + if($EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset] + + # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced + if($EncryptionValue -eq 0x02) { + $EncryptionStatus = "No" + } else { + $EncryptionStatus = "Yes" + } } else { - $EncryptionStatus = "Yes" + Write-Verbose " Encryption offset $EncryptionOffset out of range (payload length: $($ResponsePayload.Length))" } + } else { + Write-Verbose " Encryption option not found in response" + } + } else { + Write-Verbose " Response length too short: $ResponseLength" } + } else { + Write-Verbose " Failed to read response header (read $BytesRead bytes)" } $Stream.Close() $TcpClient.Close() } else { $EncryptionStatus = "Unknown" + Write-Verbose " TcpClient not connected" } } catch { $EncryptionStatus = "Unknown" + Write-Verbose " Encryption test error: $($_.Exception.Message)" } finally { if($TcpClient -ne $null) { if($TcpClient.Connected) { From 62e5134652fb124ca6a6e4f552b8e8f73523bdc1 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:03:45 +0100 Subject: [PATCH 26/31] Add dynamic port discovery and default instance check --- PowerUpSQL.ps1 | 287 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 91865c6..27e7dae 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16281,6 +16281,16 @@ Function Get-SQLInstanceDomain HelpMessage = 'Tests encryption enforcement (NTLM relay vulnerability check).')] [switch]$CheckEncryption, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Always test default port 1433 on each hostname, even if not in SPN.')] + [switch]$CheckDefaultInstance, + + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Use UDP SQL Browser (port 1434) to discover all instances dynamically on each host.')] + [switch]$DiscoverDynamicPorts, + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Preforms a DNS lookup on the instance.')] @@ -16577,6 +16587,283 @@ Function Get-SQLInstanceDomain $null = $TblSQLServerSpns.Rows.Add($TableRow) } + # Additional discovery: check default port 1433 and/or use UDP discovery + if($CheckDefaultInstance -or $DiscoverDynamicPorts) + { + # Extract unique hostnames from SPN results + $UniqueHosts = @() + if($TblSQLServers) { + $UniqueHosts = $TblSQLServers | + Select-Object -Property ComputerName -Unique | + ForEach-Object { $_.ComputerName } + } + + Write-Verbose -Message "Found $($UniqueHosts.Count) unique hosts from SPNs for additional discovery..." + + # Track instances we've already tested to avoid duplicates + $AlreadyTested = @{} + foreach($Row in $TblSQLServerSpns.Rows) { + $AlreadyTested[$Row.Instance] = $true + } + + # Process each unique hostname + foreach($HostName in $UniqueHosts) + { + Write-Verbose -Message "Processing additional discovery for $HostName..." + + # List to store newly discovered instances for this host + $NewInstances = @() + + # Check default instance on port 1433 + if($CheckDefaultInstance) + { + $DefaultInstance = "$HostName,1433" + if(-not $AlreadyTested.ContainsKey($DefaultInstance)) { + Write-Verbose -Message " Adding default instance: $DefaultInstance" + $NewInstances += @{ + ComputerName = $HostName + Instance = $DefaultInstance + InstanceName = "MSSQLSERVER" + TCPPort = 1433 + Source = "DefaultPort" + } + $AlreadyTested[$DefaultInstance] = $true + } else { + Write-Verbose -Message " Default instance $DefaultInstance already discovered via SPN" + } + } + + # Use UDP SQL Browser to discover all instances + if($DiscoverDynamicPorts) + { + Write-Verbose -Message " Running UDP discovery on $HostName..." + try { + $UDPResults = Get-SQLInstanceScanUDP -ComputerName $HostName -UDPTimeOut $UDPTimeOut -SuppressVerbose + + foreach($UDPResult in $UDPResults) { + $UDPInstance = $UDPResult.Instance + if(-not $AlreadyTested.ContainsKey($UDPInstance)) { + Write-Verbose -Message " Discovered via UDP: $UDPInstance (Port $($UDPResult.TCPPort))" + $NewInstances += @{ + ComputerName = $UDPResult.ComputerName + Instance = $UDPInstance + InstanceName = $UDPResult.InstanceName + TCPPort = $UDPResult.TCPPort + Source = "UDP" + BaseVersion = $UDPResult.BaseVersion + IsClustered = $UDPResult.IsClustered + } + $AlreadyTested[$UDPInstance] = $true + } else { + Write-Verbose -Message " Instance $UDPInstance already discovered" + } + } + } catch { + Write-Verbose -Message " UDP discovery failed: $($_.Exception.Message)" + } + } + + # Process newly discovered instances + foreach($NewInstance in $NewInstances) + { + Write-Verbose -Message " Processing newly discovered instance: $($NewInstance.Instance)" + + # Build table row with available data + $TableRow = @( + [string]$NewInstance.ComputerName, + [string]$NewInstance.Instance, + '', # DomainAccountSid - not available for non-SPN discoveries + '', # DomainAccount - not available for non-SPN discoveries + '', # DomainAccountCn - not available for non-SPN discoveries + '', # Service - not available for non-SPN discoveries + "Discovered via $($NewInstance.Source)", # Spn - indicate discovery method + '', # LastLogon - not available for non-SPN discoveries + "Discovered via $($NewInstance.Source)" # Description + ) + + # Get IP address if requested + if($IncludeIP) + { + try { + $IPAddress = [Net.DNS]::GetHostAddresses([String]$NewInstance.ComputerName).IPAddressToString + if($IPAddress -is [Object[]]) { + $IPAddress = $IPAddress -join ", " + } + } catch { + $IPAddress = "0.0.0.0" + } + $TableRow += $IPAddress + } + + # Test encryption if requested + if($CheckEncryption) + { + Write-Verbose -Message " Testing encryption on $($NewInstance.Instance)..." + $EncryptionStatus = "Unknown" + + $TestComputer = $NewInstance.ComputerName + $TestPort = $NewInstance.TCPPort + + # Test encryption using TDS pre-login + $TcpClient = $null + try { + $TcpClient = New-Object System.Net.Sockets.TcpClient + + $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne(10000, $false)) { + try { + $TcpClient.EndConnect($ConnectResult) + } catch { + $EncryptionStatus = "Unknown" + } + } else { + $EncryptionStatus = "Unknown" + } + + if($TcpClient.Connected) { + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = 10000 + $Stream.WriteTimeout = 5000 + + # Build TDS pre-login packet (same as in main loop) + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) + $EncryptionValue = @(0x02) + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + $PreLoginOptions = @( + 0x00, + ([byte]($VersionOffset -shr 8)), + ([byte]($VersionOffset -band 0xFF)), + ([byte]($Version.Length -shr 8)), + ([byte]($Version.Length -band 0xFF)), + 0x01, + ([byte]($EncryptionOffset -shr 8)), + ([byte]($EncryptionOffset -band 0xFF)), + 0x00, 0x01, + 0x02, + ([byte]($InstanceOffset -shr 8)), + ([byte]($InstanceOffset -band 0xFF)), + ([byte]($InstanceBytes.Length -shr 8)), + ([byte]($InstanceBytes.Length -band 0xFF)), + 0x03, + ([byte]($ThreadIDOffset -shr 8)), + ([byte]($ThreadIDOffset -band 0xFF)), + 0x00, 0x04, + 0xFF + ) + + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count + $TotalLength = 8 + $PayloadLength + + $TdsHeader = @( + 0x12, 0x01, + ([byte]($TotalLength -shr 8)), + ([byte]($TotalLength -band 0xFF)), + 0x00, 0x00, 0x00, 0x00 + ) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) + + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + Write-Verbose " Read $BytesRead response header bytes" + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + Write-Verbose " Response length: $ResponseLength" + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + Write-Verbose " Read $BytesRead payload bytes" + + # Find ENCRYPTION option + $i = 0 + $EncryptionOffset = -1 + + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + if($OptionType -eq 0xFF) { break } + if($OptionType -eq 0x01) { + $EncryptionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + break + } + $i += 5 + } + + if($EncryptionOffset -ge 0) { + if($EncryptionOffset -lt $ResponsePayload.Length) { + $EncryptionValue = $ResponsePayload[$EncryptionOffset] + + # Only 0x02 (TDS_ENCRYPT_NOT_SUP) means NOT enforced + if($EncryptionValue -eq 0x02) { + $EncryptionStatus = "No" + } else { + $EncryptionStatus = "Yes" + } + } else { + Write-Verbose " Encryption offset $EncryptionOffset out of range" + } + } else { + Write-Verbose " Encryption option not found in response" + } + } else { + Write-Verbose " Response length too short: $ResponseLength" + } + } else { + Write-Verbose " Failed to read response header" + } + + $Stream.Close() + $TcpClient.Close() + } + } catch { + $EncryptionStatus = "Unknown" + Write-Verbose " Encryption test error: $($_.Exception.Message)" + } finally { + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } + } + } + + $TableRow += $EncryptionStatus + Write-Verbose -Message " Encryption Enforced: $EncryptionStatus" + } + + # Add newly discovered instance to table + $null = $TblSQLServerSpns.Rows.Add($TableRow) + Write-Verbose -Message " Added to results" + } + } + } + # Enumerate SQL Server instances from management servers if($CheckMgmt) { From c742d8039efdc3569c3d5a973a4da5673f5e6ed5 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:04:13 +0100 Subject: [PATCH 27/31] Update PowerUpSQL.psd1 From d5e0364bba38c1d6bb5157f6ab086cf31a05a6da Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:31:30 +0100 Subject: [PATCH 28/31] Add QuickAudit parameters and functionality --- PowerUpSQL.ps1 | 230 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 27e7dae..614a4b8 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16291,6 +16291,24 @@ Function Get-SQLInstanceDomain HelpMessage = 'Use UDP SQL Browser (port 1434) to discover all instances dynamically on each host.')] [switch]$DiscoverDynamicPorts, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + HelpMessage = 'Perform quick audit: test login, get version, database, privileges, and sysadmin status.')] + [switch]$QuickAudit, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server login username for QuickAudit authentication.')] + [string]$SQLUsername, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server login password for QuickAudit authentication.')] + [string]$SQLPassword, + + [Parameter(Mandatory = $false, + HelpMessage = 'SQL Server credentials for QuickAudit authentication.')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()]$SQLCredential = [System.Management.Automation.PSCredential]::Empty, + [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Preforms a DNS lookup on the instance.')] @@ -16325,6 +16343,18 @@ Function Get-SQLInstanceDomain { $null = $TblSQLServerSpns.Columns.Add('EncryptionEnforced') } + + if($QuickAudit) + { + $null = $TblSQLServerSpns.Columns.Add('LoginSuccess') + $null = $TblSQLServerSpns.Columns.Add('Version') + $null = $TblSQLServerSpns.Columns.Add('CurrentLogin') + $null = $TblSQLServerSpns.Columns.Add('CurrentDatabase') + $null = $TblSQLServerSpns.Columns.Add('IsSysadmin') + $null = $TblSQLServerSpns.Columns.Add('HasXpDirtree') + $null = $TblSQLServerSpns.Columns.Add('HasXpFileexist') + $null = $TblSQLServerSpns.Columns.Add('HasXpCmdshell') + } # Table for UDP scan results of management servers } @@ -16582,6 +16612,106 @@ Function Get-SQLInstanceDomain $TableRow += $EncryptionStatus Write-Verbose -Message " Encryption Enforced: $EncryptionStatus" } + + # Perform quick audit if requested + if($QuickAudit) + { + Write-Verbose -Message "Performing quick audit on $SpnServerInstance..." + + # Initialize audit values + $LoginSuccess = "No" + $VersionInfo = "" + $CurrentLogin = "" + $CurrentDatabase = "" + $IsSysadminStatus = "No" + $HasXpDirtree = "No" + $HasXpFileexist = "No" + $HasXpCmdshell = "No" + + # Test connection + $ConnTest = Get-SQLConnectionTest -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ConnTest.Status -eq "Accessible") + { + $LoginSuccess = "Yes" + Write-Verbose -Message " Login successful" + + # Get server information + try { + $ServerInfo = Get-SQLServerInfo -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ServerInfo) + { + $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + $CurrentLogin = $ServerInfo.Currentlogin + $IsSysadminStatus = $ServerInfo.IsSysadmin + Write-Verbose -Message " Version: $VersionInfo" + Write-Verbose -Message " Current Login: $CurrentLogin" + Write-Verbose -Message " SysAdmin: $IsSysadminStatus" + } + } catch { + Write-Verbose -Message " Error getting server info: $($_.Exception.Message)" + } + + # Get current database + try { + $DBQuery = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "SELECT DB_NAME() as CurrentDB" -SuppressVerbose + if($DBQuery) { + $CurrentDatabase = $DBQuery.CurrentDB + Write-Verbose -Message " Database: $CurrentDatabase" + } + } catch { + Write-Verbose -Message " Error getting current database: $($_.Exception.Message)" + } + + # Check xp_dirtree access + try { + $XpDirtreeTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_dirtree 'C:\',0,0" -SuppressVerbose -ErrorAction SilentlyContinue + if($XpDirtreeTest -or $?) { + $HasXpDirtree = "Yes" + Write-Verbose -Message " xp_dirtree: Available" + } + } catch { + Write-Verbose -Message " xp_dirtree: Not available" + } + + # Check xp_fileexist access + try { + $XpFileexistTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_fileexist 'C:\Windows\win.ini'" -SuppressVerbose -ErrorAction SilentlyContinue + if($XpFileexistTest -or $?) { + $HasXpFileexist = "Yes" + Write-Verbose -Message " xp_fileexist: Available" + } + } catch { + Write-Verbose -Message " xp_fileexist: Not available" + } + + # Check xp_cmdshell access + try { + $XpCmdshellTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_cmdshell 'echo test'" -SuppressVerbose -ErrorAction SilentlyContinue + if($XpCmdshellTest -or $?) { + $HasXpCmdshell = "Yes" + Write-Verbose -Message " xp_cmdshell: Available" + } + } catch { + Write-Verbose -Message " xp_cmdshell: Not available" + } + } + else + { + Write-Verbose -Message " Login failed or instance not accessible" + } + + # Add audit results to table row + $TableRow += $LoginSuccess + $TableRow += $VersionInfo + $TableRow += $CurrentLogin + $TableRow += $CurrentDatabase + $TableRow += $IsSysadminStatus + $TableRow += $HasXpDirtree + $TableRow += $HasXpFileexist + $TableRow += $HasXpCmdshell + } # Add SQL Server spn to table $null = $TblSQLServerSpns.Rows.Add($TableRow) @@ -16857,6 +16987,106 @@ Function Get-SQLInstanceDomain Write-Verbose -Message " Encryption Enforced: $EncryptionStatus" } + # Perform quick audit if requested + if($QuickAudit) + { + Write-Verbose -Message " Performing quick audit on $($NewInstance.Instance)..." + + # Initialize audit values + $LoginSuccess = "No" + $VersionInfo = "" + $CurrentLogin = "" + $CurrentDatabase = "" + $IsSysadminStatus = "No" + $HasXpDirtree = "No" + $HasXpFileexist = "No" + $HasXpCmdshell = "No" + + # Test connection + $ConnTest = Get-SQLConnectionTest -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ConnTest.Status -eq "Accessible") + { + $LoginSuccess = "Yes" + Write-Verbose -Message " Login successful" + + # Get server information + try { + $ServerInfo = Get-SQLServerInfo -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose + + if($ServerInfo) + { + $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + $CurrentLogin = $ServerInfo.Currentlogin + $IsSysadminStatus = $ServerInfo.IsSysadmin + Write-Verbose -Message " Version: $VersionInfo" + Write-Verbose -Message " Current Login: $CurrentLogin" + Write-Verbose -Message " SysAdmin: $IsSysadminStatus" + } + } catch { + Write-Verbose -Message " Error getting server info: $($_.Exception.Message)" + } + + # Get current database + try { + $DBQuery = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "SELECT DB_NAME() as CurrentDB" -SuppressVerbose + if($DBQuery) { + $CurrentDatabase = $DBQuery.CurrentDB + Write-Verbose -Message " Database: $CurrentDatabase" + } + } catch { + Write-Verbose -Message " Error getting current database: $($_.Exception.Message)" + } + + # Check xp_dirtree access + try { + $XpDirtreeTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_dirtree 'C:\',0,0" -SuppressVerbose -ErrorAction SilentlyContinue + if($XpDirtreeTest -or $?) { + $HasXpDirtree = "Yes" + Write-Verbose -Message " xp_dirtree: Available" + } + } catch { + Write-Verbose -Message " xp_dirtree: Not available" + } + + # Check xp_fileexist access + try { + $XpFileexistTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_fileexist 'C:\Windows\win.ini'" -SuppressVerbose -ErrorAction SilentlyContinue + if($XpFileexistTest -or $?) { + $HasXpFileexist = "Yes" + Write-Verbose -Message " xp_fileexist: Available" + } + } catch { + Write-Verbose -Message " xp_fileexist: Not available" + } + + # Check xp_cmdshell access + try { + $XpCmdshellTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_cmdshell 'echo test'" -SuppressVerbose -ErrorAction SilentlyContinue + if($XpCmdshellTest -or $?) { + $HasXpCmdshell = "Yes" + Write-Verbose -Message " xp_cmdshell: Available" + } + } catch { + Write-Verbose -Message " xp_cmdshell: Not available" + } + } + else + { + Write-Verbose -Message " Login failed or instance not accessible" + } + + # Add audit results to table row + $TableRow += $LoginSuccess + $TableRow += $VersionInfo + $TableRow += $CurrentLogin + $TableRow += $CurrentDatabase + $TableRow += $IsSysadminStatus + $TableRow += $HasXpDirtree + $TableRow += $HasXpFileexist + $TableRow += $HasXpCmdshell + } + # Add newly discovered instance to table $null = $TblSQLServerSpns.Rows.Add($TableRow) Write-Verbose -Message " Added to results" From 37757421df39e83b7e34d0bd39c574b10fc98417 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:32:01 +0100 Subject: [PATCH 29/31] Update PowerUpSQL.psd1 From 908e3867fcdd17532c65165213c118aa0f35de34 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:39:27 +0100 Subject: [PATCH 30/31] Refactor permission checks for SQL functions --- PowerUpSQL.ps1 | 66 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index 614a4b8..c550618 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16664,37 +16664,46 @@ Function Get-SQLInstanceDomain Write-Verbose -Message " Error getting current database: $($_.Exception.Message)" } - # Check xp_dirtree access + # Check xp_dirtree access (check permissions, don't execute) try { - $XpDirtreeTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_dirtree 'C:\',0,0" -SuppressVerbose -ErrorAction SilentlyContinue - if($XpDirtreeTest -or $?) { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_dirtree', 'OBJECT', 'EXECUTE') as HasPermission" + $XpDirtreeTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpDirtreeTest.HasPermission -eq 1) { $HasXpDirtree = "Yes" Write-Verbose -Message " xp_dirtree: Available" + } else { + Write-Verbose -Message " xp_dirtree: No permission" } } catch { - Write-Verbose -Message " xp_dirtree: Not available" + Write-Verbose -Message " xp_dirtree: Check failed - $($_.Exception.Message)" } - # Check xp_fileexist access + # Check xp_fileexist access (check permissions, don't execute) try { - $XpFileexistTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_fileexist 'C:\Windows\win.ini'" -SuppressVerbose -ErrorAction SilentlyContinue - if($XpFileexistTest -or $?) { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_fileexist', 'OBJECT', 'EXECUTE') as HasPermission" + $XpFileexistTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpFileexistTest.HasPermission -eq 1) { $HasXpFileexist = "Yes" Write-Verbose -Message " xp_fileexist: Available" + } else { + Write-Verbose -Message " xp_fileexist: No permission" } } catch { - Write-Verbose -Message " xp_fileexist: Not available" + Write-Verbose -Message " xp_fileexist: Check failed - $($_.Exception.Message)" } - # Check xp_cmdshell access + # Check xp_cmdshell access (check permissions, don't execute) try { - $XpCmdshellTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_cmdshell 'echo test'" -SuppressVerbose -ErrorAction SilentlyContinue - if($XpCmdshellTest -or $?) { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_cmdshell', 'OBJECT', 'EXECUTE') as HasPermission" + $XpCmdshellTest = Get-SQLQuery -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpCmdshellTest.HasPermission -eq 1) { $HasXpCmdshell = "Yes" Write-Verbose -Message " xp_cmdshell: Available" + } else { + Write-Verbose -Message " xp_cmdshell: No permission" } } catch { - Write-Verbose -Message " xp_cmdshell: Not available" + Write-Verbose -Message " xp_cmdshell: Check failed - $($_.Exception.Message)" } } else @@ -17038,37 +17047,46 @@ Function Get-SQLInstanceDomain Write-Verbose -Message " Error getting current database: $($_.Exception.Message)" } - # Check xp_dirtree access + # Check xp_dirtree access (check permissions, don't execute) try { - $XpDirtreeTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_dirtree 'C:\',0,0" -SuppressVerbose -ErrorAction SilentlyContinue - if($XpDirtreeTest -or $?) { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_dirtree', 'OBJECT', 'EXECUTE') as HasPermission" + $XpDirtreeTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpDirtreeTest.HasPermission -eq 1) { $HasXpDirtree = "Yes" Write-Verbose -Message " xp_dirtree: Available" + } else { + Write-Verbose -Message " xp_dirtree: No permission" } } catch { - Write-Verbose -Message " xp_dirtree: Not available" + Write-Verbose -Message " xp_dirtree: Check failed - $($_.Exception.Message)" } - # Check xp_fileexist access + # Check xp_fileexist access (check permissions, don't execute) try { - $XpFileexistTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_fileexist 'C:\Windows\win.ini'" -SuppressVerbose -ErrorAction SilentlyContinue - if($XpFileexistTest -or $?) { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_fileexist', 'OBJECT', 'EXECUTE') as HasPermission" + $XpFileexistTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpFileexistTest.HasPermission -eq 1) { $HasXpFileexist = "Yes" Write-Verbose -Message " xp_fileexist: Available" + } else { + Write-Verbose -Message " xp_fileexist: No permission" } } catch { - Write-Verbose -Message " xp_fileexist: Not available" + Write-Verbose -Message " xp_fileexist: Check failed - $($_.Exception.Message)" } - # Check xp_cmdshell access + # Check xp_cmdshell access (check permissions, don't execute) try { - $XpCmdshellTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query "EXEC master..xp_cmdshell 'echo test'" -SuppressVerbose -ErrorAction SilentlyContinue - if($XpCmdshellTest -or $?) { + $XpPrivQuery = "SELECT HAS_PERMS_BY_NAME('master..xp_cmdshell', 'OBJECT', 'EXECUTE') as HasPermission" + $XpCmdshellTest = Get-SQLQuery -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -Query $XpPrivQuery -SuppressVerbose -TimeOut 5 + if($XpCmdshellTest.HasPermission -eq 1) { $HasXpCmdshell = "Yes" Write-Verbose -Message " xp_cmdshell: Available" + } else { + Write-Verbose -Message " xp_cmdshell: No permission" } } catch { - Write-Verbose -Message " xp_cmdshell: Not available" + Write-Verbose -Message " xp_cmdshell: Check failed - $($_.Exception.Message)" } } else From 63acd7129515820cb34d0c76ec32a4de31f293b0 Mon Sep 17 00:00:00 2001 From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:49:21 +0100 Subject: [PATCH 31/31] Enhance TDS pre-login version detection logic --- PowerUpSQL.ps1 | 166 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 6 deletions(-) diff --git a/PowerUpSQL.ps1 b/PowerUpSQL.ps1 index c550618..22252ce 100644 --- a/PowerUpSQL.ps1 +++ b/PowerUpSQL.ps1 @@ -16628,6 +16628,154 @@ Function Get-SQLInstanceDomain $HasXpFileexist = "No" $HasXpCmdshell = "No" + # Get version via unauthenticated TDS pre-login (before authentication attempt) + $TestPort = 1433 + $TestComputer = "" + if($SpnServerInstance -match ',') + { + $TestPort = $SpnServerInstance.Split(',')[1] + $TestComputer = $SpnServerInstance.Split(',')[0] + } + elseif($SpnServerInstance -match '\\') + { + $TestComputer = $SpnServerInstance.Split('\\')[0] + $TestInstanceName = $SpnServerInstance.Split('\\')[1] + try { + $UDPResult = Get-SQLInstanceScanUDP -ComputerName $TestComputer -SuppressVerbose | + Where-Object {$_.InstanceName -eq $TestInstanceName} | + Select-Object -First 1 + if($UDPResult -and $UDPResult.TCPPort) { + $TestPort = $UDPResult.TCPPort + } + } catch { } + } + else + { + $TestComputer = $SpnServerInstance + } + + # Try to get version via TDS pre-login + if($TestPort -and $TestComputer) + { + $TcpClient = $null + try { + $TcpClient = New-Object System.Net.Sockets.TcpClient + $ConnectResult = $TcpClient.BeginConnect($TestComputer, $TestPort, $null, $null) + $WaitHandle = $ConnectResult.AsyncWaitHandle + + if($WaitHandle.WaitOne(5000, $false)) { + try { $TcpClient.EndConnect($ConnectResult) } catch { } + } + + if($TcpClient.Connected) { + $Stream = $TcpClient.GetStream() + $Stream.ReadTimeout = 5000 + $Stream.WriteTimeout = 3000 + + # Build TDS pre-login packet + $Version = @(0x08, 0x00, 0x01, 0x55, 0x00, 0x00) + $EncryptionValue = @(0x02) + $InstanceBytes = [System.Text.Encoding]::ASCII.GetBytes("MSSQLServer`0") + $ThreadIDBytes = [BitConverter]::GetBytes([uint32](Get-Random -Maximum 65535)) + + $VersionOffset = 21 + $EncryptionOffset = $VersionOffset + $Version.Length + $InstanceOffset = $EncryptionOffset + $EncryptionValue.Length + $ThreadIDOffset = $InstanceOffset + $InstanceBytes.Length + + $PreLoginOptions = @( + 0x00, ([byte]($VersionOffset -shr 8)), ([byte]($VersionOffset -band 0xFF)), + ([byte]($Version.Length -shr 8)), ([byte]($Version.Length -band 0xFF)), + 0x01, ([byte]($EncryptionOffset -shr 8)), ([byte]($EncryptionOffset -band 0xFF)), 0x00, 0x01, + 0x02, ([byte]($InstanceOffset -shr 8)), ([byte]($InstanceOffset -band 0xFF)), + ([byte]($InstanceBytes.Length -shr 8)), ([byte]($InstanceBytes.Length -band 0xFF)), + 0x03, ([byte]($ThreadIDOffset -shr 8)), ([byte]($ThreadIDOffset -band 0xFF)), 0x00, 0x04, + 0xFF + ) + + $PreLoginData = [System.Collections.ArrayList]@() + $null = $PreLoginData.AddRange($Version) + $null = $PreLoginData.AddRange($EncryptionValue) + $null = $PreLoginData.AddRange($InstanceBytes) + $null = $PreLoginData.AddRange($ThreadIDBytes) + + $PreLoginPayload = [System.Collections.ArrayList]@() + $null = $PreLoginPayload.AddRange($PreLoginOptions) + $null = $PreLoginPayload.AddRange($PreLoginData) + + $PayloadLength = $PreLoginPayload.Count + $TotalLength = 8 + $PayloadLength + + $TdsHeader = @(0x12, 0x01, ([byte]($TotalLength -shr 8)), ([byte]($TotalLength -band 0xFF)), 0x00, 0x00, 0x00, 0x00) + + $TdsPacket = [System.Collections.ArrayList]@() + $null = $TdsPacket.AddRange($TdsHeader) + $null = $TdsPacket.AddRange($PreLoginPayload) + + $PacketBytes = [byte[]]$TdsPacket.ToArray() + $Stream.Write($PacketBytes, 0, $PacketBytes.Length) + $Stream.Flush() + + $ResponseHeader = New-Object byte[] 8 + $BytesRead = $Stream.Read($ResponseHeader, 0, 8) + + if($BytesRead -eq 8) { + $ResponseLength = ([int]$ResponseHeader[2] -shl 8) + [int]$ResponseHeader[3] + + if($ResponseLength -gt 8) { + $ResponsePayload = New-Object byte[] ($ResponseLength - 8) + $BytesRead = $Stream.Read($ResponsePayload, 0, $ResponsePayload.Length) + + # Parse VERSION option (token 0x00) + $i = 0 + while($i -lt $ResponsePayload.Length) { + $OptionType = $ResponsePayload[$i] + if($OptionType -eq 0xFF) { break } + if($OptionType -eq 0x00) { + $VersionOffset = ([int]$ResponsePayload[$i+1] -shl 8) + [int]$ResponsePayload[$i+2] + if($VersionOffset -lt $ResponsePayload.Length - 6) { + $Major = $ResponsePayload[$VersionOffset] + $Minor = $ResponsePayload[$VersionOffset + 1] + $Build = ([int]$ResponsePayload[$VersionOffset + 2] -shl 8) + [int]$ResponsePayload[$VersionOffset + 3] + + # Map version to SQL Server version name + $VersionName = switch($Major) { + 16 { "2022" } + 15 { "2019" } + 14 { "2017" } + 13 { "2016" } + 12 { "2014" } + 11 { "2012" } + 10 { "2008/2008R2" } + 9 { "2005" } + default { "Unknown" } + } + + $VersionInfo = "$VersionName ($Major.$Minor.$Build)" + Write-Verbose -Message " Version (TDS): $VersionInfo" + } + break + } + $i += 5 + } + } + } + + $Stream.Close() + $TcpClient.Close() + } + } catch { + Write-Verbose -Message " TDS version detection failed: $($_.Exception.Message)" + } finally { + if($TcpClient -ne $null) { + if($TcpClient.Connected) { + try { $TcpClient.Close() } catch { } + } + try { $TcpClient.Dispose() } catch { } + } + } + } + # Test connection $ConnTest = Get-SQLConnectionTest -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose @@ -16636,16 +16784,19 @@ Function Get-SQLInstanceDomain $LoginSuccess = "Yes" Write-Verbose -Message " Login successful" - # Get server information + # Get server information (login and sysadmin status) try { $ServerInfo = Get-SQLServerInfo -Instance $SpnServerInstance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose if($ServerInfo) { - $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + # Only update version if we didn't get it from TDS + if(-not $VersionInfo) { + $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + Write-Verbose -Message " Version (Authenticated): $VersionInfo" + } $CurrentLogin = $ServerInfo.Currentlogin $IsSysadminStatus = $ServerInfo.IsSysadmin - Write-Verbose -Message " Version: $VersionInfo" Write-Verbose -Message " Current Login: $CurrentLogin" Write-Verbose -Message " SysAdmin: $IsSysadminStatus" } @@ -17019,16 +17170,19 @@ Function Get-SQLInstanceDomain $LoginSuccess = "Yes" Write-Verbose -Message " Login successful" - # Get server information + # Get server information (login and sysadmin status) try { $ServerInfo = Get-SQLServerInfo -Instance $NewInstance.Instance -Username $SQLUsername -Password $SQLPassword -Credential $SQLCredential -SuppressVerbose if($ServerInfo) { - $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + # Only update version if we didn't get it already (from UDP or TDS) + if(-not $VersionInfo) { + $VersionInfo = "$($ServerInfo.SQLServerMajorVersion) ($($ServerInfo.SQLServerVersionNumber))" + Write-Verbose -Message " Version (Authenticated): $VersionInfo" + } $CurrentLogin = $ServerInfo.Currentlogin $IsSysadminStatus = $ServerInfo.IsSysadmin - Write-Verbose -Message " Version: $VersionInfo" Write-Verbose -Message " Current Login: $CurrentLogin" Write-Verbose -Message " SysAdmin: $IsSysadminStatus" }