Skip to content

Commit 375e963

Browse files
Get-SqlServerTlsCertificate - Make compatible with PowerShell v3
- Replace PowerShell class with Add-Type C# block (classes require PS v5+) - Replace all ::new() constructor calls with New-Object (PS v3 compatible) - Remove .GetAwaiter().GetResult() call (.NET 4.0 compatible; .Wait() already handles the result) - Extract SSL validation callback to named variable for New-Object compatibility (do Get-DbaNetworkEncryption) Co-authored-by: Andreas Jordan <andreasjordan@users.noreply.github.com>
1 parent 5c411a3 commit 375e963

1 file changed

Lines changed: 80 additions & 71 deletions

File tree

private/functions/Get-SqlServerTlsCertificate.ps1

Lines changed: 80 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -111,71 +111,80 @@ Function Get-SqlServerTlsCertificate {
111111
$StrictEncrypt
112112
)
113113

114-
class TdsTlsStream : System.IO.Stream {
115-
[System.IO.Stream]$InnerStream
116-
[int]$PayloadLength = 0
114+
if (-not ([System.Management.Automation.PSTypeName]"TdsTlsStream").Type) {
115+
Add-Type -TypeDefinition @"
116+
using System;
117+
using System.IO;
117118
118-
TdsTlsStream([System.IO.Stream]$InnerStream) {
119-
$this.InnerStream = $InnerStream
120-
}
119+
public class TdsTlsStream : Stream {
120+
private Stream _innerStream;
121+
private int _payloadLength = 0;
121122
122-
[bool] get_CanRead() { return $this.InnerStream.CanRead }
123-
[bool] get_CanWrite() { return $this.InnerStream.CanWrite }
124-
[bool] get_CanSeek() { return $this.InnerStream.CanSeek }
125-
[Int64] get_Length() { return $this.InnerStream.Length }
126-
[Int64] get_Position() { return $this.InnerStream.Position }
127-
[void] set_Position([Int64]$Value) { $this.InnerStream.Position = $Value }
128-
[int] get_ReadTimeout() { return $this.InnerStream.ReadTimeout }
129-
[int] get_WriteTimeout() { return $this.InnerStream.WriteTimeout }
130-
131-
[void] Flush() { $this.InnerStream.Flush() }
132-
[Int64] Seek([Int64]$Offset, [System.IO.SeekOrigin]$Origin) { return $this.InnerStream.Seek($Offset, $Origin) }
133-
[void] SetLength([Int64]$Value) { $this.InnerStream.SetLength($Value) }
134-
135-
[int] Read([byte[]]$Buffer, [int]$Offset, [int]$Count) {
136-
# We need to strip off the TDS header before setting the Buffer
137-
if ($this.PayloadLength -eq 0) {
138-
$header = [byte[]]::new(8)
139-
$read = 0
140-
while ($read -lt 8) {
141-
$read += $this.InnerStream.Read($header, 0, 8)
142-
}
123+
public TdsTlsStream(Stream innerStream) {
124+
_innerStream = innerStream;
125+
}
143126
144-
$lengthBeforeHeader = [System.BitConverter]::ToUInt16([byte[]]@($header[3], $header[2]), 0)
145-
$lengthBeforeHeader -= 8
146-
$this.PayloadLength = $lengthBeforeHeader
127+
public override bool CanRead { get { return _innerStream.CanRead; } }
128+
public override bool CanWrite { get { return _innerStream.CanWrite; } }
129+
public override bool CanSeek { get { return _innerStream.CanSeek; } }
130+
public override long Length { get { return _innerStream.Length; } }
131+
public override long Position {
132+
get { return _innerStream.Position; }
133+
set { _innerStream.Position = value; }
134+
}
135+
public override int ReadTimeout { get { return _innerStream.ReadTimeout; } }
136+
public override int WriteTimeout { get { return _innerStream.WriteTimeout; } }
137+
138+
public override void Flush() { _innerStream.Flush(); }
139+
public override long Seek(long offset, SeekOrigin origin) { return _innerStream.Seek(offset, origin); }
140+
public override void SetLength(long value) { _innerStream.SetLength(value); }
141+
142+
public override int Read(byte[] buffer, int offset, int count) {
143+
// We need to strip off the TDS header before setting the Buffer
144+
if (_payloadLength == 0) {
145+
byte[] header = new byte[8];
146+
int read = 0;
147+
while (read < 8) {
148+
read += _innerStream.Read(header, 0, 8);
147149
}
148150
149-
if ($Count -gt $this.PayloadLength) {
150-
$Count = $this.PayloadLength
151-
}
152-
$read = $this.InnerStream.Read($Buffer, $Offset, $Count)
153-
$this.PayloadLength -= $read
154-
return $read
151+
int lengthBeforeHeader = (int)BitConverter.ToUInt16(new byte[] { header[3], header[2] }, 0);
152+
lengthBeforeHeader -= 8;
153+
_payloadLength = lengthBeforeHeader;
155154
}
156155
157-
[void] Write([byte[]]$Buffer, [int]$Offset, [int]$Count) {
158-
$newPayload = $this.GenerateTdsHeader($Buffer, $Offset, $Count)
159-
$this.InnerStream.Write($newPayload, 0, $newPayload.Length)
156+
if (count > _payloadLength) {
157+
count = _payloadLength;
160158
}
159+
int bytesRead = _innerStream.Read(buffer, offset, count);
160+
_payloadLength -= bytesRead;
161+
return bytesRead;
162+
}
161163
162-
[byte[]] GenerateTdsHeader([byte[]]$Payload, [int]$Offset, [int]$Count) {
163-
# The length is big endian encoded so it is inserted in reverse order
164-
$lengthBytes = [System.BitConverter]::GetBytes([uint16]($Count + 8))
165-
166-
$newPayload = [byte[]]::new(8 + $Count)
167-
$newPayload[0] = 0x12 # Type - Pre-Login
168-
$newPayload[1] = 0x01 # Status - End of message (EOM)
169-
$newPayload[2] = $lengthBytes[1]
170-
$newPayload[3] = $lengthBytes[0]
171-
$newPayload[4] = 0 # SPID
172-
$newPayload[5] = 0 # SPID
173-
$newPayload[6] = 0 # PacketID
174-
$newPayload[7] = 0 # Window
175-
[System.Array]::Copy($Payload, $Offset, $newPayload, 8, $Count)
176-
177-
return $newPayload
178-
}
164+
public override void Write(byte[] buffer, int offset, int count) {
165+
byte[] newPayload = GenerateTdsHeader(buffer, offset, count);
166+
_innerStream.Write(newPayload, 0, newPayload.Length);
167+
}
168+
169+
private byte[] GenerateTdsHeader(byte[] payload, int offset, int count) {
170+
// The length is big endian encoded so it is inserted in reverse order
171+
byte[] lengthBytes = BitConverter.GetBytes((ushort)(count + 8));
172+
173+
byte[] newPayload = new byte[8 + count];
174+
newPayload[0] = 0x12; // Type - Pre-Login
175+
newPayload[1] = 0x01; // Status - End of message (EOM)
176+
newPayload[2] = lengthBytes[1];
177+
newPayload[3] = lengthBytes[0];
178+
newPayload[4] = 0; // SPID
179+
newPayload[5] = 0; // SPID
180+
newPayload[6] = 0; // PacketID
181+
newPayload[7] = 0; // Window
182+
Array.Copy(payload, offset, newPayload, 8, count);
183+
184+
return newPayload;
185+
}
186+
}
187+
"@
179188
}
180189

181190
$udpClient = $socket = $targetStream = $sslStream = $null
@@ -190,7 +199,7 @@ Function Get-SqlServerTlsCertificate {
190199
if ($ConnectionType -eq "SQLBrowser") {
191200
# Use the SQLBrowser
192201
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/2e1560c9-5097-4023-9f5e-72b9ff1ec3b1
193-
$udpClient = [System.Net.Sockets.UdpClient]::new($ComputerName, 1434)
202+
$udpClient = New-Object -TypeName System.Net.Sockets.UdpClient -ArgumentList @($ComputerName, 1434)
194203
$udpClient.Client.SendTimeout = $ConnectTimeout
195204
$udpClient.Client.ReceiveTimeout = $ConnectTimeout
196205
$null = $udpClient.Send([byte[]]@(0x03), 1) # CLNT_UCAST_EX
@@ -239,18 +248,17 @@ Function Get-SqlServerTlsCertificate {
239248
if ($ConnectionType -eq "TCP") {
240249
Write-Verbose -Message "Connecting to TCP/IP endpoint $($ComputerName):$Port"
241250

242-
$socket = [System.Net.Sockets.TcpClient]::new()
251+
$socket = New-Object -TypeName System.Net.Sockets.TcpClient
243252
$connectTask = $socket.ConnectAsync($ComputerName, $Port)
244253
if (-not $connectTask.Wait($ConnectTimeout)) {
245254
throw "Timed out connecting to TCP/IP endpoint $($ComputerName):$Port"
246255
}
247256

248-
$null = $connectTask.GetAwaiter().GetResult()
249257
$targetStream = $socket.GetStream()
250258
}
251259
else {
252260
Write-Verbose -Message "Connecting to Named Pipe endpoint \\$($ComputerName)\pipe\$pipeName"
253-
$targetStream = [System.IO.Pipes.NamedPipeClientStream]::new(
261+
$targetStream = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList @(
254262
$ComputerName,
255263
$pipeName,
256264
[System.IO.Pipes.PipeDirection]::InOut)
@@ -281,7 +289,7 @@ Function Get-SqlServerTlsCertificate {
281289
)
282290
$targetStream.Write($tdsPreLogin, 0, $tdsPreLogin.Count)
283291

284-
$headerBytes = [byte[]]::new(8)
292+
$headerBytes = New-Object byte[] 8
285293
$read = 0
286294
while ($read -ne $headerBytes.Length) {
287295
$read += $targetStream.Read($headerBytes, $read, $headerBytes.Length - $read)
@@ -292,7 +300,7 @@ Function Get-SqlServerTlsCertificate {
292300
$payloadLength = [System.BitConverter]::ToUInt16([byte[]]@($headerBytes[3], $headerBytes[2]), 0)
293301
$payloadLength -= 8
294302

295-
$tdsPreLoginResp = [byte[]]::new($payloadLength)
303+
$tdsPreLoginResp = New-Object byte[] $payloadLength
296304
$read = 0
297305
while ($read -ne $tdsPreLoginResp.Length) {
298306
$read += $targetStream.Read($tdsPreLoginResp, $read, $tdsPreLoginResp.Length - $read)
@@ -337,21 +345,22 @@ Function Get-SqlServerTlsCertificate {
337345
# there is a note that TDS 7.1 or earlier (SQL Server 2000 or earlier)
338346
# should use the table response type (0x04) instead. As this is so old
339347
# I'm not going to implement that.
340-
$streamToWrap = [TdsTlsStream]::new($targetStream)
348+
$streamToWrap = New-Object -TypeName TdsTlsStream -ArgumentList $targetStream
341349
}
342350

343351
# Create the SslStream with a disable certificate verification callback.
344352
# This allows it to connect to a self signed or cert with different
345353
# hostname. The callback will also capture more information about the peer
346354
# Allows us to emit warnings if it was going to fail.
347355
$certState = @{}
348-
$sslStream = [System.Net.Security.SslStream]::new($streamToWrap, $false, {
349-
param($Sender, $Certificate, $Chain, $SslPolicyErrors)
356+
$sslValidationCallback = [System.Net.Security.RemoteCertificateValidationCallback] {
357+
param($Sender, $Certificate, $Chain, $SslPolicyErrors)
350358

351-
$certState.Chain = $chain
352-
$certState.SslPolicyErrors = $SslPolicyErrors
353-
$true
354-
})
359+
$certState.Chain = $Chain
360+
$certState.SslPolicyErrors = $SslPolicyErrors
361+
$true
362+
}
363+
$sslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($streamToWrap, $false, $sslValidationCallback)
355364
Write-Verbose -Message "Starting TLS Handshake"
356365
$sslStream.AuthenticateAsClient($ComputerName)
357366
Write-Verbose -Message "TLS result: $($certState.SslPolicyErrors)"
@@ -364,7 +373,7 @@ Function Get-SqlServerTlsCertificate {
364373
Write-Warning -Message $msg.TrimEnd()
365374
}
366375

367-
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sslStream.RemoteCertificate)
376+
$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $sslStream.RemoteCertificate
368377
Write-Verbose -Message "Found cert for $($cert.Subject), Expires: $($cert.NotAfter), SANs: $($cert.DnsNameList -join ", ")"
369378

370379
$cert
@@ -378,4 +387,4 @@ Function Get-SqlServerTlsCertificate {
378387
if ($targetStream) { $targetStream.Dispose() }
379388
if ($socket) { $socket.Dispose() }
380389
}
381-
}
390+
}

0 commit comments

Comments
 (0)