Как наиболее эффективно обнаружить все запущенные экземпляры 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.

Elsimer
источник
stackoverflow.com/questions/7516337/…
Аарон Бертран

Ответы:

12

Если вы хотите что-то, что будет полезно в будущем, я, вероятно, воздержусь от попыток поиска в реестре. Ульи для SQL Server несколько изменились за эти годы, и это может быть хлопотно, чтобы не отставать.

Время от времени метод SqlDataSourceEnumeratorявляется нестабильным, и хотя я буду его использовать, нет конкретных доказательств того, что экземпляры находятся в сети. Я считаю, что это также зависит от службы браузера SQL, которая в большинстве случаев оказывается отключенной.

Я буду использовать класс WMI win32_Service. Я использую это, потому что он предоставляет больше информации об услуге, чем Get-Serviceкомандлет.

Обычно я пишу все как функции, потому что вы можете использовать это, чтобы фактически выполнять ежедневную проверку или проверку службы для устранения неполадок.

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

Это немного больше, чем я обычно использую, но в случае, если кто-то еще сталкивается и хочет использовать это. Значение Test-Connectionравно ping myserverв приглашении DOS, и -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

РЕДАКТИРОВАТЬ

Отмеченный комментарий заключается в том, что вышесказанное зависит от списка предоставляемых серверов. В тех случаях, когда мне не предоставлен этот список, у вас есть несколько других вариантов.

  • Если я нахожусь в среде 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

РЕДАКТИРОВАТЬ

Предлагаемое редактирование для использования, 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
        }
    }
JamieSee
источник
2

Еще один способ определения возможных экземпляров SQL - посмотреть на имена принципов обслуживания (SPN), перечисленные в Active Directory. При удаленном подключении к SQL Server с помощью проверки подлинности Windows в процессе проверки подлинности используется имя участника-службы. Наличие имени участника-службы не означает, что сервер / экземпляр определенно существует и работает, но оно дает вам список возможных экземпляров, которые, как я обнаружил, являются более полными, чем некоторые другие подходы.

Чтобы упростить жизнь, я использую командлет 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

(Очевидно, что вы можете сохранить скрипт в другом месте, просто обновите первую строку по мере необходимости.)

В этом списке будут перечислены все имена участников-служб SQL Server в текущем домене, включая «спецификацию», относящуюся к порту / экземпляру службы.

Matt
источник
Я заметил, что большинство наших машин SQL Server не могут получить имена участников-служб (или некоторые такие предупреждения в журнале обслуживания). Будут ли они все еще появляться с использованием этого сценария?
Эльсимер
Обычно это происходит потому, что служба работает как пользователь, который не является администратором домена или локальной системой (требуется для создания SPN). Администратор домена, вероятно, добавил имена участников-служб с помощью утилиты SetSPN и своей учетной записи администратора домена, чтобы аутентификация домена работала правильно для этих компьютеров. Скорее всего, да.
Мэтт
0

Get-Service -ComputerName * MSSQL * | Where-Object {$ _. Status -eq "Running"}

Это должно получить именованные и стандартные экземпляры. Просто повторите ваш список машин и т. Д.

user41207
источник
-4

Только что попробовал это: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Возвращено не так много данных, но он обнаружил все серверы sql, которые я использую в среде виртуальных машин.

Шон
источник
6
Этот метод уже включен в ответ Томаса Стрингера .
MDCCL