Apa cara paling efektif untuk menemukan semua instance SQL Server yang menggunakan PowerShell?

13

Saya telah ditugaskan untuk menemukan semua contoh SQL Server yang berjalan dalam domain kami. Dalam beberapa kasus ada beberapa contoh per server. Saya telah melihat dua metode PowerShell berbeda untuk menemukan instance ini, tetapi tampaknya tidak menemukan semua instance.

1) Gunakan 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) Gunakan remote registry (seperti pada Get-SQLInstance 1 )

Masalah terbesar yang saya hadapi adalah bahwa tidak semua server yang saya tahu menjalankan dengan penyedia WMI SQL Server juga tidak semuanya memungkinkan remote registry. Apakah ada metode ketiga? Saya dapat menggunakan Remote Desktop untuk mengakses semua server tapi saya melihat sekitar 30 mesin dan ingin menghindari langkah-langkah manual jika memungkinkan. Ini hanya perlu bekerja untuk SQL Server 2008 dan lebih tinggi dan sementara itu akan menyenangkan untuk mengetahui tentang layanan SQL Server lainnya (SSIS / SSAS / SSRS) fokus utama saya adalah pada SQL Server itu sendiri.

Elsimer
sumber

Jawaban:

12

Jika Anda menginginkan sesuatu yang akan berguna untuk masa depan saya mungkin akan menghindari mencoba mencari registri. Sarang untuk SQL Server telah sedikit berubah selama bertahun-tahun dan itu bisa menyulitkan untuk mengikutinya.

Metode dengan SqlDataSourceEnumeratorflaky pada waktu dan meskipun saya akan menggunakannya, bukan bukti nyata bahwa instance ada di jaringan. Saya percaya ini tergantung pada Layanan Peramban SQL juga, yang sebagian besar waktu saya temukan dinonaktifkan.

Saya akan memanfaatkan kelas WMI win32_Service. Saya menggunakan ini karena ia menawarkan lebih banyak informasi tentang layanan daripada Get-Servicecmdlet.

Saya menulis semuanya sebagai fungsi secara umum karena Anda dapat menggunakan ini untuk benar-benar hanya melakukan pemeriksaan harian atau verifikasi layanan untuk pemecahan masalah.

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
   }
 }
}

Ini sedikit lebih dari apa yang biasanya saya gunakan tetapi kalau-kalau ada orang lain yang ingin menggunakannya. The Test-Connectionsetara dengan ping myserverdi DOS prompt dan -Quietbendera hanya hanya memiliki itu kembali trueatau false. Ini akan default ke 4 ping jadi pengaturan -Count 2hanya membuatnya melakukannya dua kali sebagai gantinya.

Variabel [string[]]$serveradalah metode yang digunakan untuk menyatakan bahwa $serverakan menerima array nama server. Jadi contoh panggilan fungsi ini dapat terlihat seperti:

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

atau

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

EDIT

Komentar yang dicatat adalah di atas tergantung pada daftar server yang disediakan. Dalam kasus di mana saya tidak diberikan daftar itu Anda memiliki beberapa opsi lain.

  • Jika saya berada di lingkungan Active Directory saya dapat menggunakan modul ActiveDirectory di PowerShell untuk menarik daftar semua server pada domain dengan Get-ADComputercmdlet. Namun kata peringatan pastikan Anda menggunakan barang bagus -Filterdi domain besar.

  • Saya juga hanya melakukan pemindaian IP (dengan persetujuan) dari jaringan yang memberi saya alamat IP tempat port 1433 ditemukan terbuka. Saya akan mengambil daftar IP itu dan menggunakannya Get-ADComputeruntuk menemukan nama komputer domain, lalu meneruskannya ke fungsi di atas

Contoh:

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

EDIT

Edit yang disarankan untuk digunakan Write-Verbosedan juga ditambahkan dalam blok coba / tangkap, sementara itu mungkin berguna, dan dalam kebanyakan kasus praktik kode, saya akan menyerahkannya kepada orang yang ingin menggunakan fungsi ini untuk menambahkan kode atau fungsi tambahan itu. Hanya berusaha memberikan contoh dasar untuk melanjutkan. Saya memang menambahkan SystemNameproperti ke output untuk memasukkan informasi pengembalian nama server yang sebenarnya, lakukan ini pada fungsi-fungsi lain yang umumnya tidak menggunakan ini untuk lebih dari satu server pada satu waktu sehingga menyelinap pikiran saya.


sumber
Itu berfungsi asalkan Anda diberikan daftar server untuk memulai. Itu tidak selalu bisa diasumsikan.
Thomas Stringer
Hanya untuk kejelasan membatasi pemindaian ke port 1433 akan meninggalkan server apa pun yang hanya bernama instance (atau dengan instance default hard-coded untuk menggunakan port yang berbeda). Mungkin bukan masalah besar tetapi ada banyak orang paranoid di luar sana yang menutup perusahaan pelabuhan itu.
Aaron Bertrand
Benar, itu hanya titik awal. Yang mana port umumnya diatur Saya telah menemukan klien biasanya memiliki server-server yang dicatat (menyadarinya). Memang menemukan metode ini oleh Brian Kelley tetapi belum mencobanya.
Saya pikir menggabungkan kedua metode registri dan WMI win32_service sebagai mundur harus mendapatkan mayoritas server, dan kemudian pencarian manual sisanya akan bekerja. Sebagai efek samping yang menyenangkan, saya juga dapat menarik beberapa informasi tentang layanan yang berjalan tetapi tidak diperlukan, server yang tidak memungkinkan saya mengakses, dll.
Elsimer
5

Satu-satunya cara yang saya tahu untuk menemukan contoh di lingkungan tanpa mengetahui semua server yang mungkin memiliki dan nama-nama khusus mereka, akan membuat panggilan ke System.Data.Sql.SqlDataSourceEnumerator.GetDataSources (). Metode ini dilengkapi dengan banyak catatan kaki. Berikut ini cuplikan yang ditarik langsung dari sumber daya MSDN:

Karena sifat mekanisme yang digunakan oleh SqlDataSourceEnumerator untuk mencari sumber data di jaringan, metode ini tidak akan selalu mengembalikan daftar lengkap dari server yang tersedia , dan daftar mungkin tidak sama pada setiap panggilan. Jika Anda berencana untuk menggunakan fungsi ini untuk memungkinkan pengguna memilih server dari daftar, pastikan Anda selalu menyediakan opsi untuk mengetikkan nama yang tidak ada dalam daftar, seandainya enumerasi server tidak mengembalikan semua server yang tersedia . Selain itu, metode ini mungkin memerlukan banyak waktu untuk dieksekusi , jadi berhati-hatilah saat memanggilnya ketika kinerja sangat penting.

Panggilannya sederhana dari PowerShell:

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

Metode itu mengembalikan DataTableobjek yang dapat Anda tangani sesuai.

Thomas Stringer
sumber
3

Jika Layanan Peramban SQL aktif, Anda dapat meminta layanan untuk SQL Instances dengan kode PowerShell di bawah ini. Itu mengimplementasikan commandlets berikut untuk melakukan query:

  • Dapatkan-SqlBrowserInstanceList
  • Dapatkan-SqlBrowserInstanceInfo
  • Dapatkan-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
        }
    }
JamieSee
sumber
2

Cara lain untuk mengidentifikasi kemungkinan SQL Instances adalah dengan melihat Service Principle Names (SPNs) yang terdaftar di Active Directory. Ketika Anda terhubung ke SQL Server jarak jauh dengan Windows Authentication, SPN digunakan dalam proses otentikasi. Kehadiran SPN tidak berarti bahwa server / instance pasti ada dan berjalan tetapi itu memberi Anda daftar kemungkinan contoh yang saya temukan lebih komprehensif dari beberapa pendekatan lain.

Untuk membuat hidup lebih mudah, saya menggunakan cmdlet Get-SPN dari: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Unduh skrip Get-SPN.ps1, simpan ke C: \ powershell_scripts \ Get-SPN.ps1 dan jalankan yang berikut di PowerShell:

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

(Jelas Anda dapat menyimpan skrip ke lokasi lain, cukup perbarui baris pertama sesuai kebutuhan.)

Ini akan mencantumkan semua SQL Server SPN pada domain saat ini, termasuk "spesifikasi" yang berkaitan dengan port / instance layanan.

Mat
sumber
Saya perhatikan bahwa sebagian besar mesin SQL Server kami tidak dapat memperoleh SPN (atau peringatan semacam itu di log pemeliharaan). Akankah mereka tetap muncul menggunakan skrip itu?
Elsimer
Itu biasanya karena layanan berjalan sebagai pengguna yang bukan admin domain atau sistem lokal (diperlukan untuk membuat SPN). Admin domain mungkin telah menambahkan SPN menggunakan utilitas SetSPN dan akun admin domainnya, sehingga domain auth berfungsi dengan baik untuk mesin ini. Sangat mungkin, ya.
Matt
0

Dapatkan-Layanan -KomputerName * MSSQL * | Where-Object {$ _. Status -eq "Running"}

Itu harus diberi nama dan contoh default. Ulangi daftar mesin Anda, dll.

pengguna41207
sumber
-4

Baru saja mencoba ini: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Tidak banyak data yang dikembalikan tetapi mendeteksi semua server sql yang saya jalankan di lingkungan VM.

Sean
sumber
6
Metode itu sudah termasuk dalam jawaban Thomas Stringer .
MDCCL