Cách hiệu quả nhất để khám phá tất cả các phiên bản đang chạy của SQL Server bằng PowerShell là gì?


13

Tôi đã được giao nhiệm vụ khám phá tất cả các phiên bản của SQL Server đang chạy trong miền của chúng tôi. Trong một số trường hợp có nhiều trường hợp cho mỗi máy chủ. Tôi đã thấy hai phương pháp PowerShell khác nhau để tìm các phiên bản này, nhưng dường như không tìm thấy tất cả các phiên bản.

1) Sử dụng WMI

        $srvr = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer $computerName
    $instances = $srvr | ForEach-Object {$_.ServerInstances} | Select @{Name="fullName";Expression={$computerName +"\"+ $_.Name}}   
    return $instances

2) Sử dụng đăng ký từ xa (như với Get-SQLInstance 1 )

Vấn đề lớn nhất mà tôi gặp phải là không phải tất cả các máy chủ mà tôi biết đều đang chạy với nhà cung cấp SQL Server WMI cũng như tất cả chúng đều cho phép đăng ký từ xa. Có một phương pháp thứ ba? Tôi có thể sử dụng Remote Desktop để truy cập tất cả các máy chủ nhưng tôi đang xem khoảng 30 máy và muốn tránh các bước thủ công nếu có thể. Điều này chỉ cần hoạt động cho SQL Server 2008 trở lên và trong khi thật tuyệt khi biết về các dịch vụ SQL Server khác (SSIS / SSAS / SSRS), trọng tâm chính của tôi là về chính SQL Server.


Câu trả lời:


12

Nếu bạn muốn một cái gì đó sẽ hữu ích cho tương lai, có lẽ tôi sẽ tránh xa việc cố gắng tìm kiếm sổ đăng ký. Các tổ ong cho SQL Server đã thay đổi một chút trong những năm qua và nó có thể gây rắc rối để theo kịp.

Phương thức với sự SqlDataSourceEnumeratorkhông ổn định đôi khi và mặc dù tôi sẽ sử dụng nó, không phải là bằng chứng cụ thể cho thấy các trường hợp trên mạng. Tôi tin rằng nó cũng phụ thuộc vào Dịch vụ Trình duyệt SQL, phần lớn thời gian tôi thấy bị vô hiệu hóa.

Tôi sẽ sử dụng lớp WMI win32_Service. Tôi sử dụng điều này bởi vì nó cung cấp nhiều thông tin về dịch vụ hơn Get-Servicecmdlet.

Tôi viết mọi thứ như các chức năng nói chung vì bạn có thể sử dụng điều này để thực sự kiểm tra hàng ngày hoặc xác minh dịch vụ để khắc phục sự cố.

function Get-ServiceStatus ([string[]]$server)
{
 foreach ($s in $server)
 {
   if(Test-Connection $s -Count 2 -Quiet)
   {
    Get-WmiObject win32_Service -Computer $s |
     where {$_.DisplayName -match "SQL Server"} | 
     select SystemName, DisplayName, Name, State, Status, StartMode, StartName
   }
 }
}

Đây là một chút nhiều hơn những gì tôi thường sử dụng nhưng trong trường hợp người khác đi qua và muốn sử dụng nó. Tương Test-Connectionđương với ping myservertrong dấu nhắc DOS và -Quietcờ chỉ cần trả về truehoặc false. Điều này sẽ mặc định là 4 ping để thiết lập -Count 2chỉ làm cho nó thực hiện hai lần thay thế.

Biến [string[]]$serverlà một phương thức được sử dụng để tuyên bố $serversẽ chấp nhận một mảng các tên máy chủ. Vì vậy, một cuộc gọi ví dụ của chức năng này có thể trông giống như:

Get-ServiceStatus -server (Get-Content C:\temp\MyServerList.txt)

hoặc là

$servers = 'MyServer1','MyServer2','MyServer3'
Get-ServiceStatus -server $servers

BIÊN TẬP

Một nhận xét được lưu ý là ở trên không phụ thuộc vào danh sách các máy chủ được cung cấp. Trong trường hợp tôi không được cung cấp danh sách đó, bạn có một vài lựa chọn khác.

  • Nếu tôi ở trong môi trường Active Directory, tôi có thể sử dụng mô-đun ActiveDirectory trong PowerShell để lấy danh sách tất cả các máy chủ trên miền bằng Get-ADComputerlệnh ghép ngắn. Một lời cảnh báo mặc dù đảm bảo bạn sử dụng hàng hóa -Filtertrên các tên miền lớn.

  • Tôi cũng chỉ đơn giản là thực hiện quét IP (có sự chấp thuận) của một mạng cung cấp cho tôi các địa chỉ IP nơi cổng 1433 được tìm thấy mở. Tôi sẽ lấy danh sách IP đó và sử dụng Get-ADComputerđể tìm tên máy tính miền, sau đó chuyển nó vào chức năng trên

Thí dụ:

Import-Module ActiveDirectory
$sList = $ipList | Select -ExpandProperty IP
$results = foreach ($i in $sList) { 
 Get-ADComputer -Filter 'IPv4Address -eq $i' -Properties * | Select Name}
Get-ServiceStatus -server $results

BIÊN TẬP

Chỉnh sửa được đề xuất để sử dụng Write-Verbosevà cũng thêm vào khối thử / bắt, trong khi điều đó có thể hữu ích và trong hầu hết các trường hợp thực hành mã, tôi sẽ để lại cho người muốn sử dụng chức năng này để thêm mã hoặc chức năng bổ sung đó. Chỉ cần cố gắng cung cấp một ví dụ cơ bản để tiếp tục. Tôi đã thêm thuộc SystemNametính vào đầu ra để bao gồm thông tin trả về tên máy chủ thực tế, làm điều này trên các chức năng khác thường không sử dụng điều này cho nhiều máy chủ cùng một lúc để nó bị mất trí.


Điều đó hoạt động miễn là bạn được cung cấp một danh sách các máy chủ để bắt đầu. Điều đó không thể luôn luôn được giả định.
Thomas Stringer

Để rõ ràng, việc giới hạn quá trình quét vào cổng 1433 sẽ loại bỏ bất kỳ máy chủ nào chỉ có các phiên bản được đặt tên (hoặc với các phiên bản mặc định được mã hóa cứng để sử dụng một cổng khác). Có thể không phải là một vấn đề lớn nhưng có rất nhiều người hoang tưởng ngoài kia đã đóng cửa cảng toàn doanh nghiệp.
Aaron Bertrand

Đúng, nó chỉ là một điểm khởi đầu. Những cái mà các cổng thường được đặt tôi đã tìm thấy các máy khách thường có các máy chủ được ghi chú (nhận biết về chúng). Đã tìm thấy phương pháp này của Brian Kelley nhưng chưa thử.

Tôi nghĩ rằng việc kết hợp cả phương thức đăng ký và WMI win32_service làm dự phòng sẽ có được phần lớn các máy chủ và sau đó tìm kiếm thủ công của phần còn lại sẽ hoạt động. Là một tác dụng phụ thú vị, tôi cũng có thể lấy một số thông tin về các dịch vụ đang chạy nhưng không cần thiết, các máy chủ không cho phép tôi truy cập, v.v.
Elsimer

5

Cách duy nhất mà tôi biết để khám phá các thể hiện trên một môi trường mà không biết tất cả các máy chủ sở hữu có thể và tên cụ thể của chúng, sẽ là gọi đến System.Data.Sql.SqlDataSourceEnumerator.GetDataSource (). Phương pháp này đi kèm với rất nhiều chú thích, mặc dù. Đây là một đoạn được lấy trực tiếp từ tài nguyên MSDN đó:

Do bản chất của cơ chế được SqlDataSourceEnumerator sử dụng để định vị nguồn dữ liệu trên mạng, phương thức sẽ không luôn trả về một danh sách đầy đủ các máy chủ có sẵn và danh sách có thể không giống nhau trên mỗi cuộc gọi. Nếu bạn dự định sử dụng chức năng này để cho phép người dùng chọn máy chủ từ danh sách, hãy đảm bảo rằng bạn luôn cung cấp tùy chọn để nhập tên không có trong danh sách, trong trường hợp liệt kê máy chủ không trả về tất cả các máy chủ có sẵn . Ngoài ra, phương pháp này có thể mất một lượng thời gian đáng kể để thực hiện , vì vậy hãy cẩn thận khi gọi nó khi hiệu suất rất quan trọng.

Cuộc gọi rất đơn giản từ PowerShell:

[System.Data.Sql.SqlDataSourceEnumerator]::Instance.GetDataSources()

Phương thức đó trả về một DataTableđối tượng mà bạn có thể xử lý tương ứng.


3

Nếu Dịch vụ Trình duyệt SQL đang hoạt động, bạn có thể truy vấn dịch vụ cho Trường hợp SQL bằng mã PowerShell bên dưới. Nó thực hiện các lệnh sau để thực hiện các truy vấn:

  • Get-SqlBrowserInstanceList
  • Get-SqlBrowserInstanceInfo
  • Get-SqlBrowserInstanceDac

    function Parse-ServerResponse([byte[]] $responseData)
    {
        [PSObject[]] $instances = @()
    
        if (($responseData -ne $null) -and ($responseData[0] -eq 0x05))
        {
            $responseSize = [System.BitConverter]::ToInt16($responseData, 1)
    
            if ($responseSize -le $responseData.Length - 3)
            {
                # Discard any bytes beyond the received response size. An oversized response is usually the result of receiving multiple replies to a broadcast request.
                $responseString = [System.Text.Encoding]::Default.GetString(($responseData | Select -Skip 3 -First $responseSize))
                $instanceResponses = $responseString.Split(@(";;"), [System.StringSplitOptions]::RemoveEmptyEntries)
    
                $instances = foreach ($instanceResponse in $instanceResponses)
                {
                    $instanceResponseValues = $instanceResponse.Split(";")
                    $instanceResponseHash = @{}
                    for ($index = 0; $index -lt $instanceResponseValues.Length; $index += 2)
                    {
                        $instanceResponseHash[$instanceResponseValues[$index]] = $instanceResponseValues[$index + 1]
                    }
    
                    New-Object PSObject -Property $instanceResponseHash
                }
            }
            else
            {
                Write-Warning "The response was too short. Expected $($responseSize) bytes but got $($responseData.Length - 3)."
            }
        }
    
        return ,$instances
    }
    
    function Parse-ServerResponseDac([byte[]] $responseData)
    {
        $dacPort = 0
    
        if (($responseData -ne $null) -and ($responseData[0] -eq 0x05))
        {
            $responseSize = [System.BitConverter]::ToUInt16($responseData, 1)
    
            if (($responseData.Length -eq 6) -and ($responseSize -eq 6))
            {
                if ($responseData[3] -eq 0x01)
                {
                    $dacPort = [System.BitConverter]::ToUInt16($responseData, 4)
                }
                else
                {
                    Write-Error "An unexpected protocol version was returned. Expected 0x01 but got $($requestData[3])."
                }
            }
            else
            {
                Write-Error "The response size was incorrect."
            }
        }
    
        return $dacPort
    }
    
    function Get-SqlBrowserInstanceList
    {
        <#
        .SYNOPSIS
        Gets the list of available SQL Instances on the server.
        .DESCRIPTION
        Gets the list of available SQL Instances on the server by querying the SQL Browser Service on port 1434.
        .EXAMPLE
        Get-SqlBrowserInstanceList servername
        .EXAMPLE
        Get-SqlBrowserInstanceList servername.dnsdomain.tld
        .EXAMPLE
        Get-SqlBrowserInstanceList $env:COMPUTERNAME
        .EXAMPLE
        Get-SqlBrowserInstanceList 192.168.1.255 -Broadcast
        .EXAMPLE
        Get-SqlBrowserInstanceList 255.255.255.255 -Broadcast
        .PARAMETER $ServerName
        The name or IP Address of the server.
        .PARAMETER $Broadcast
        If the broadcast switch is specified, the query will be sent as a broadcast and may receive replies from multiple hosts; otherwise, the query is sent to a single server.
        #>
        [CmdletBinding(SupportsShouldProcess = $False)]
        param
        (
            [Parameter(Mandatory = $True, ValueFromPipeLine = $True)]
            [string] $ServerName,
            [switch] $Broadcast
        )
    
        process
        {   
            [System.Net.IPAddress] $ipAddress = [System.Net.Dns]::GetHostAddresses($serverName) | Select -First 1
            $parsedResponses = @()
    
            if ($ipAddress -ne $null)
            {
                [System.Net.IPEndPoint] $localIPEndPoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)
                [System.Net.IPEndPoint] $remoteIPEndPoint = New-Object System.Net.IPEndPoint($ipAddress, 1434)
    
                if ($ipAddress -eq [System.Net.IPAddress]::Broadcast)
                {
                    $Broadcast = $true
                }
    
                [System.Net.Sockets.UdpClient] $receiver = New-Object System.Net.Sockets.UdpClient
                $receiver.Client.ReceiveTimeout = 30000
    
                [byte] $queryMode = 0x03
                $sleepDuration = 1
                [System.Net.Sockets.UdpClient] $sender = $null
    
                if ($Broadcast -eq $true)
                {
                    Write-Verbose "Using broadcast mode."
                    $queryMode = 0x02
                    $sleepDuration = 30
    
                    # Set the receiver to allow another client on the same socket.
                    $receiver.Client.SetSocketOption([System.Net.Sockets.SocketOptionLevel]::Socket, [System.Net.Sockets.SocketOptionName]::ReuseAddress, $true)
                    $receiver.Client.Bind($localIPEndPoint)
    
                    # Because broadcasting from this UdpClient instance causes the underlying socket to be unable to receive normally, a separate sender must be bound to the same socket as the receiver.
                    # NOTE: Windows Firewall does not view a reused socket as being part of the same conversation. If Windows Firewall is active, this requires special firewall rules to work.
                    $sender = New-Object System.Net.Sockets.UdpClient
                    $sender.EnableBroadcast = $Broadcast
                    $sender.Client.SetSocketOption([System.Net.Sockets.SocketOptionLevel]::Socket, [System.Net.Sockets.SocketOptionName]::ReuseAddress, $true)
                    $sender.Client.Bind($receiver.Client.LocalEndPoint);
                }
                else
                {
                    $sender = $receiver
                    $receiver.Client.Bind($localIPEndPoint)
                }
    
    
                $responses = @{}
    
                try
                {
                    # Send the broadcast.
                    Write-Verbose "Sending request to $($ipAddress)..."
                    $sender.Connect($remoteIPEndPoint)
                    $bytesSent = $sender.Send(@($queryMode), 1)
    
                    # Wait to give responses time to arrive.
                    Sleep $sleepDuration
    
                    do
                    {
                        [System.Net.IPEndPoint] $responderIPEndPoint = $null
                        $response = $receiver.Receive([ref] $responderIPEndPoint)
                        $responder = $responderIPEndPoint.ToString()
    
                        if ($responses.Contains($responder))
                        {
                            $responses[$responder] += $response
                        }
                        else
                        {
                            $responses.Add($responder, $response)
                        }
                    } while ($receiver.Available -gt 0)
                }
                finally
                {
                    if ($sender -ne $receiver)
                    {
                        $sender.Close()
                        $sender.Dispose()
                    }
    
                    $receiver.Close()
                    $receiver.Dispose()
                }
    
                foreach ($responseItem in $responses.GetEnumerator())
                {
                    Write-Verbose "Parsing the response from $($responseItem.Name)..."
                    $parsedResponse = Parse-ServerResponse $responseItem.Value
                    $parsedResponses += $parsedResponse
                    Write-Verbose ($parsedResponse | ft ServerName, InstanceName, tcp, np, Version, IsClustered -AutoSize |Out-String)
                }
            }
    
            return $parsedResponses
        }
    }
    
    function Get-SqlBrowserInstanceInfo
    {
        <#
        .SYNOPSIS
        Gets information about the specified SQL Instance from the server.
        .DESCRIPTION
        Gets information about the specified SQL Instance from the server by querying the SQL Browser Service on port 1434.
        .EXAMPLE
        Get-SqlBrowserInstanceInfo servername instancename
        .EXAMPLE
        Get-SqlBrowserInstanceInfo servername.dnsdomain.tld instancename
        .EXAMPLE
        Get-SqlBrowserInstanceInfo $env:COMPUTERNAME
        .PARAMETER $ServerName
        The name or IP Address of the server.
        .PARAMETER $InstanceName
        The name of the SQL Instance.    #>
        [CmdletBinding(SupportsShouldProcess = $False)]
        param
        (
            [Parameter(Mandatory = $True, ValueFromPipeLine = $True)]
            [string] $ServerName,
            [Parameter(Mandatory = $True, ValueFromPipeLine = $False)]
            [string] $InstanceName
        )
    
        process
        {   
            $instances = @()
            [System.Net.IPAddress] $ipAddress = $null
    
            $ipAddress = [System.Net.Dns]::GetHostAddresses($serverName) | Select -First 1
    
            if ($ipAddress -ne $null)
            {
                [System.Net.IPEndPoint] $ipEndPoint = New-Object System.Net.IPEndPoint($ipAddress, 1434)
                [System.Net.Sockets.UdpClient] $udpClient = New-Object System.Net.Sockets.UdpClient
                $udpClient.Client.ReceiveTimeout = 10000
    
                $instanceNameData = [System.Text.Encoding]::Default.GetBytes($instanceName)
                [byte[]] $requestData = @(0x04) + $instanceNameData + 0x00
                [byte[]] $responseData = $null
    
                try
                {
                    $udpClient.Connect($ipEndPoint)
    
                    $bytesSent = $udpClient.Send($requestData, $requestData.Length)
    
                    $responseData = do
                    {
                        $udpClient.Receive([ref] $ipEndPoint)
                    } while ($udpClient.Available -gt 0)
                }
                finally
                {
                    $udpClient.Close()
                    $udpClient.Dispose()
                }
    
                $instances = Parse-ServerResponse $responseData
            }
    
            return $instances
        }
    }
    
    function Get-SqlBrowserInstanceDac
    {
        <#
        .SYNOPSIS
        Gets the Dedicated Administrator Connection port number for the specified SQL Instance on the server.
        .DESCRIPTION
        Gets the Dedicated Administrator Connection port number for the specified SQL Instance on the server by querying the SQL Browser Service on port 1434.
        .EXAMPLE
        Get-SqlBrowserInstanceDac servername instancename
        .EXAMPLE
        Get-SqlBrowserInstanceDac servername.dnsdomain.tld instancename
        .EXAMPLE
        Get-SqlBrowserInstanceDac $env:COMPUTERNAME instancename
        .PARAMETER $ServerName
        The name or IP Address of the server.
        .PARAMETER $InstanceName
        The name of the SQL Instance.
        #>
        [CmdletBinding(SupportsShouldProcess = $False)]
        param
        (
            [Parameter(Mandatory = $True, ValueFromPipeLine = $True)]
            [string] $ServerName,
            [Parameter(Mandatory = $True, ValueFromPipeLine = $False)]
            [string] $InstanceName
        )
    
        process
        {   
            [System.UInt16] $dacPort = 0
            [System.Net.IPAddress] $ipAddress = $null
    
            $ipAddress = [System.Net.Dns]::GetHostAddresses($serverName) | Select -First 1
    
            if ($ipAddress -ne $null)
            {
                [System.Net.IPEndPoint] $ipEndPoint = New-Object System.Net.IPEndPoint($ipAddress, 1434)
                [System.Net.Sockets.UdpClient] $udpClient = New-Object System.Net.Sockets.UdpClient
                $udpClient.Client.ReceiveTimeout = 30000
    
                $instanceNameData = [System.Text.Encoding]::Default.GetBytes($instanceName)
                [byte[]] $requestData = @(0x0F) + 0x01 + $instanceNameData + 0x00
                [byte[]] $responseData = $null
    
                try
                {
                    $udpClient.Connect($ipEndPoint)
    
                    $bytesSent = $udpClient.Send($requestData, $requestData.Length)
    
                    $responseData = do
                    {
                        $udpClient.Receive([ref] $ipEndPoint)
                    } while ($udpClient.Available -gt 0)
                }
                finally
                {
                    $udpClient.Close()
                    $udpClient.Dispose()
                }
    
                $dacPort = Parse-ServerResponseDac($responseData)
            }
    
            return $dacPort
        }
    }

2

Một cách khác để xác định các Trường hợp SQL có thể là xem xét Tên Nguyên tắc Dịch vụ (SPN) được liệt kê trong Active Directory. Khi bạn kết nối với SQL Server từ xa với Xác thực Windows, SPN được sử dụng trong quy trình xác thực. Sự hiện diện của SPN không có nghĩa là máy chủ / cá thể chắc chắn ở đó và đang chạy nhưng nó cung cấp cho bạn một danh sách các trường hợp có thể mà tôi thấy là toàn diện hơn đối với một số phương pháp khác.

Để làm cho cuộc sống dễ dàng hơn, tôi sử dụng lệnh ghép ngắn Get-SPN từ: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Tải xuống tập lệnh Get-SPN.ps1, lưu tập lệnh vào C: \ powershell_scripts \ Get-SPN.ps1 và chạy phần sau trong PowerShell:

. "C:\powershell_scripts\Get-SPN.ps1"
Get-SPN -ServiceClass MSSQLSvc

(Rõ ràng bạn có thể lưu tập lệnh sang vị trí khác, chỉ cần cập nhật dòng đầu tiên theo yêu cầu.)

Điều này sẽ liệt kê tất cả các SPN của SQL Server trên miền hiện tại, bao gồm cả "đặc tả" liên quan đến cổng / thể hiện của dịch vụ.


Tôi đã nhận thấy rằng hầu hết các máy SQL Server của chúng tôi không thể nhận SPN (hoặc một số cảnh báo như vậy trong nhật ký bảo trì). Họ vẫn sẽ xuất hiện bằng cách sử dụng kịch bản đó chứ?
Elsimer

Điều đó thường là do dịch vụ chạy như một người dùng không phải là quản trị viên tên miền hoặc hệ thống cục bộ (bắt buộc phải tạo SPN). Quản trị viên tên miền có thể đã thêm SPN bằng tiện ích SetSPN và tài khoản quản trị miền của họ, để xác thực tên miền hoạt động chính xác cho các máy này. Vì vậy, có khả năng, có.
Matt

0

Nhận dịch vụ -ComputerName * MSSQL * | Trường hợp-Đối tượng {$ _. Status -eq "Đang chạy"}

Điều đó sẽ được đặt tên và trường hợp mặc định. Chỉ cần lặp lại danh sách các máy của bạn, vv


-4

Chỉ cần thử điều này: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSource ()

Không có nhiều dữ liệu được trả về nhưng nó đã phát hiện tất cả các máy chủ sql tôi đang chạy trong môi trường VM.


6
Phương pháp đó đã được bao gồm trong câu trả lời của Thomas Stringer .
MDCCL
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.