Який найефективніший спосіб виявити всі запущені екземпляри SQL Server за допомогою PowerShell?


13

Мені доручено виявити всі екземпляри SQL Server, які працюють у нашому домені. У кількох випадках на сервері є декілька примірників. Я бачив два різні способи PowerShell пошуку цих примірників, але, схоже, жоден не знайшов усіх примірників.

1) Використовуйте 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) Використовуйте віддалений реєстр (як у Get-SQLInstance 1 )

Найбільша проблема, з якою я стикаюся, полягає в тому, що не всі сервери, про які я знаю, працюють з постачальником WMI-сервера SQL Server, а також не всі вони дозволяють віддалений реєстр. Чи є третій метод? Я можу використовувати віддалений робочий стіл для доступу до всіх серверів, але я переглядаю приблизно 30 машин і хотів би уникнути ручних кроків, якщо це можливо. Для цього потрібно працювати лише для SQL Server 2008 та новіших версій, і хоча було б непогано знати про інші сервіси SQL Server (SSIS / SSAS / SSRS), моя основна увага приділяється самему SQL Server.


Відповіді:


12

Якщо ви хочете щось корисне для майбутнього, я, мабуть, уникатимуть спроб пошуку реєстру. Вулики для SQL Server протягом багатьох років дещо змінилися, і це може бути складно.

Метод із методом SqlDataSourceEnumeratorв'язаний часом, і хоча я буду його використовувати, не конкретні докази того, що екземпляри є в мережі. Я вважаю, що це залежить також від служби браузера SQL, яка більшість часу я вважаю відключеною.

Я буду використовувати клас WMI win32_Service. Я використовую це, тому що він пропонує більше інформації про послугу, ніж Get-Servicecmdlet.

Я пишу все як функції, як правило, тому що ви можете використовувати це, щоб насправді просто щодня перевіряти або перевіряти службу для усунення несправностей.

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

Це трохи більше, ніж я зазвичай використовую, але у випадку, якщо хтось інший натрапив і захоче ним скористатися. У DOS-підказку Test-Connectionприрівнюється, ping myserverі -Quietпрапор просто повертається trueабо false. Це за замовчуванням 4 пінг, так що налаштування -Count 2просто змушує це робити два рази.

Змінна [string[]]$server- це метод, який використовується для того, $serverщоб приймати масив імен серверів. Тож приклад виклику цієї функції може виглядати приблизно так:

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

або

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

EDIT

Зауважений коментар: вищезазначене залежить від списку наданих серверів. У випадках, коли мені не надано цей список, у вас є кілька інших варіантів.

  • Якщо я перебуваю в середовищі Active Directory, я можу використовувати модуль ActiveDirectory в PowerShell, щоб витягнути список усіх серверів у домені з Get-ADComputerкомандлетом. Слово попередження, хоча переконайтеся, що ви використовуєте товар -Filterу великих доменах.

  • Я також просто зробив сканування IP (з схваленням) мережі, яка дає мені IP-адреси, де порт 1433 був відкритий. Я візьму цей список IP-адрес і використаю Get-ADComputerдля пошуку імен доменних комп'ютерів, а потім передам це у функцію вище

Приклад:

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

Пропоноване редагування для використання, Write-Verboseа також додавання в блок "try / catch", хоча це може бути корисним, а в більшості випадків - кодовою практикою, я залишаю це особі, яка хоче використовувати цю функцію, щоб додати цей додатковий код або функціональність. Просто намагаюся надати основний приклад для продовження. Я додавав SystemNameвластивість до висновку, щоб включати фактичну інформацію, що повертає ім'я сервера, роби це на інших функціях, просто, як правило, не використовую це для більше ніж одного сервера за один раз, так що це прослізнуло мені в голову.


Це працює за умови, що вам надається список серверів, для початку. Це не завжди можна припустити.
Томас Стрінгер

Що стосується ясності, що обмежує сканування до порту 1433, не залишаться будь-які сервери з лише названими екземплярами (або з екземплярами за замовчуванням, жорстко кодованими для використання іншого порту). Можливо, це не велика справа, але є безліч параноїчних людей, які закрили цей порт на території підприємства.
Аарон Бертран

Правда, це лише відправна точка. У тих портах, де зазвичай встановлені порти, я виявив, що клієнти, як правило, відзначають ці сервери (знають про них). Знайшов цей метод Брайан Келлі, але не спробував.

Я думаю, що поєднання як методу реєстру, так і WMI win32_service як резервного має отримати більшість серверів, і тоді ручний пошук залишку буде працювати. Як приємний побічний ефект, я також можу отримати інформацію про сервіси, які працюють, але вони не потрібні, сервери, які не дозволяють мені отримати доступ тощо.
Elsimer

5

Єдиний спосіб, який мені відомо про виявлення екземплярів у середовищі, не знаючи всіх можливих серверів, що володіють, та їх конкретних імен, - це зателефонувати на System.Data.Sql.SqlDataSourceEnumerator.GetDataSources (). Однак цей метод має багато виносок. Ось фрагмент, який витягується безпосередньо з цього ресурсу MSDN:

Через характер механізму, який використовує SqlDataSourceEnumerator для пошуку джерел даних у мережі, метод не завжди повертає повний список доступних серверів , і список може бути не однаковим при кожному виклику. Якщо ви плануєте використовувати цю функцію, щоб дозволити користувачам вибирати сервер зі списку, переконайтеся, що ви завжди також надаєте можливість вводити ім’я, яке не є в списку, якщо перелік серверів не поверне всі доступні сервери . Крім того, цей метод може зайняти значну кількість часу для виконання , тому будьте обережні щодо виклику його, коли продуктивність є критичною.

Виклик від PowerShell простий:

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

Цей метод повертає DataTableоб'єкт, з яким ви можете відповідно обробити.


3

Якщо служба браузера SQL активна, ви можете запитати службу для SQL інстанцій з кодом PowerShell нижче. Він реалізує такі командлети для виконання запитів:

  • 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

Іншим способом ідентифікації можливих екземплярів SQL є перегляд імен принципів обслуговування (SPN), перелічених у Active Directory. Під час віддаленого підключення до SQL Server за допомогою аутентифікації Windows SPN використовується в процесі аутентифікації. Наявність SPN не означає, що сервер / екземпляр, безумовно, є і працює, але він дає вам список можливих випадків, які, як я вважав, є більш вичерпними деякі інші підходи.

Для полегшення життя я використовую командлет Get-SPN від: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Завантажте сценарій Get-SPN.ps1, збережіть його на C: \ powershell_scripts \ Get-SPN.ps1 та запустіть у PowerShell таке:

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

(Очевидно, ви можете зберегти сценарій в іншому місці, просто оновіть перший рядок у міру необхідності.)

Тут буде перераховано всі SPN-адреси SQL Server у поточному домені, включаючи "специфікацію", що стосується порту / примірника служби.


Я помітив, що більшість наших машин SQL Server не в змозі отримати SPN (або якесь таке попередження в журналі технічного обслуговування). Чи все-таки вони з’являться за допомогою цього сценарію?
Ельсімер

Це зазвичай тому, що служба працює як користувач, який не є адміністратором домену або локальною системою (необхідний для створення SPN). Адміністратор домену, ймовірно, додав SPN за допомогою утиліти SetSPN та їх облікового запису адміністратора домену, щоб автентифікація домену працювала належним чином для цих машин. Тому, ймовірно, так.
Метт

0

Get-Service -ComputerName * MSSQL * | Where-Object {$ _. Status -eq "Запуск"}

Це має отримати іменні та типові екземпляри. Просто повторіть список машин тощо.


-4

Щойно спробував це: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Повернуто не багато даних, але він виявив всі сервери sql, на яких я працюю в середовищі VM.


6
Цей метод вже включений в Томаса Стрінгер відповіді .
MDCCL
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.