Qual é a maneira mais eficaz de descobrir todas as instâncias em execução do SQL Server usando o PowerShell?

13

Fui encarregado de descobrir todas as instâncias do SQL Server em execução em nosso domínio. Em vários casos, existem várias instâncias por servidor. Eu já vi dois métodos diferentes do PowerShell para localizar essas instâncias, mas nenhum deles parece encontrar todas as instâncias.

1) Use 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) Use o registro remoto (como no Get-SQLInstance 1 )

O maior problema que encontro é que nem todos os servidores que eu conheço estão em execução com o provedor WMI do SQL Server nem permitem o registro remoto. Existe um terceiro método? Posso usar a Área de trabalho remota para acessar todos os servidores, mas estou vendo aproximadamente 30 máquinas e, se possível, evito etapas manuais. Isso só precisa funcionar para o SQL Server 2008 e superior. Embora seja interessante saber sobre os outros serviços do SQL Server (SSIS / SSAS / SSRS), meu foco principal é o próprio SQL Server.

Elsimer
fonte

Respostas:

12

Se você quiser algo que será útil para o futuro, provavelmente evitaria tentar pesquisar no registro. As seções do SQL Server mudaram um pouco ao longo dos anos e pode ser problemático acompanhar.

O método com o SqlDataSourceEnumeratoré esquisito às vezes e, embora eu o use, não há evidências concretas de que instâncias estejam na rede. Acredito que depende também do SQL Browser Service, que na maioria das vezes acho desativado.

Vou utilizar a classe WMI win32_Service. Eu uso isso porque ele oferece mais informações sobre o serviço do que o Get-Servicecmdlet.

Geralmente, escrevo tudo como funções, porque você pode usá-lo para apenas verificar diariamente ou verificar o serviço para solucionar problemas.

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

Isso é um pouco mais do que eu costumo usar, mas no caso de alguém aparecer e querer usá-lo. Os Test-Connectionequivale a ping myserverem um prompt do DOS e da -Quietbandeira simplesmente tem que voltar trueou false. O padrão será 4 pings, portanto, a configuração -Count 2apenas o faz duas vezes.

A variável [string[]]$serveré um método usado para declarar que $serveraceitará uma matriz de nomes de servidores. Portanto, um exemplo de chamada dessa função pode ser algo como:

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

ou

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

EDITAR

Um comentário observado é que o acima depende de uma lista de servidores sendo fornecidos. Nos casos em que não recebo essa lista, você tem algumas outras opções.

  • Se estiver em um ambiente do Active Directory, posso usar o módulo ActiveDirectory no PowerShell para obter uma lista de todos os servidores no domínio com Get-ADComputercmdlet. Uma palavra de aviso, porém, certifique-se de usar um bem -Filterem domínios grandes.

  • Também fiz simplesmente uma varredura IP (com aprovação) de uma rede que me fornece os endereços IP em que a porta 1433 foi encontrada aberta. Vou pegar essa lista de IP e utilizá-la Get-ADComputerpara encontrar os nomes dos computadores de domínio e depois passá-los para a função acima

Exemplo:

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

EDITAR

A edição sugerida para utilizar Write-Verbosee também adicionar o bloco try / catch, embora possa ser útil e, na maioria dos casos, uma prática de código, deixarei isso para a pessoa que deseja usar essa função para adicionar esse código ou funcionalidade adicional. Apenas tentando fornecer um exemplo básico para continuar. Eu adicionei a SystemNamepropriedade à saída para incluir o nome real do servidor, retornando informações; faça isso em outras funções, geralmente não use isso para mais de um servidor por vez, de modo que isso me escapou da cabeça.


fonte
Isso funciona, desde que você seja fornecido com uma lista de servidores para começar. Isso nem sempre pode ser assumido.
Thomas Stringer
Por uma questão de clareza, a limitação da varredura à porta 1433 deixará de fora quaisquer servidores com apenas instâncias nomeadas (ou com instâncias padrão codificadas para usar uma porta diferente). Talvez não seja grande coisa, mas há muitas pessoas paranóicas por aí que fecharam o porto em toda a empresa.
Aaron Bertrand
É verdade que é apenas um ponto de partida. Aqueles em que as portas geralmente são definidas, encontrei clientes geralmente têm esses servidores anotados (cientes deles). Encontrou este método por Brian Kelley, mas ainda não o tentou.
Acho que a combinação do método de registro e do WMI win32_service como fallback deve obter a maioria dos servidores e, em seguida, uma pesquisa manual do restante funcionará. Como um efeito colateral agradável, eu também pode puxar algumas informações sobre os serviços que estão em execução, mas não são necessários, os servidores que não estão permitindo o acesso me, etc.
Elsimer
5

A única maneira que conheço de descobrir instâncias em um ambiente sem conhecer todos os possíveis servidores proprietários e seus nomes específicos, seria fazer uma chamada para System.Data.Sql.SqlDataSourceEnumerator.GetDataSources (). Esse método é fornecido com muitas notas de rodapé. Aqui está um trecho extraído diretamente desse recurso do MSDN:

Devido à natureza do mecanismo usado pelo SqlDataSourceEnumerator para localizar fontes de dados em uma rede, o método nem sempre retorna uma lista completa dos servidores disponíveis , e a lista pode não ser a mesma em todas as chamadas. Se você planeja usar esta função para permitir que os usuários selecionem um servidor em uma lista, sempre forneça uma opção para digitar um nome que não esteja na lista, caso a enumeração do servidor não retorne todos os servidores disponíveis . Além disso, esse método pode levar uma quantidade significativa de tempo para ser executado ; portanto, tenha cuidado ao chamá-lo quando o desempenho for crítico.

A chamada é simples no PowerShell:

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

Esse método retorna um DataTableobjeto que você pode manipular adequadamente.

Thomas Stringer
fonte
3

Se o Serviço Navegador SQL estiver ativo, você poderá consultar Instâncias SQL com o código do PowerShell abaixo. Ele implementa os seguintes comandos para executar as consultas:

  • 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
fonte
2

Outra maneira de identificar possíveis instâncias SQL é examinar os SPNs (nomes de princípios de serviço) listados no Active Directory. Quando você se conecta remotamente ao SQL Server com a Autenticação do Windows, um SPN é usado no processo de autenticação. A presença de um SPN não significa que o servidor / instância esteja definitivamente em execução, mas fornece uma lista de possíveis instâncias que eu achei mais abrangentes em algumas das outras abordagens.

Para facilitar a vida, uso o cmdlet Get-SPN em: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Baixe o script Get-SPN.ps1, salve-o em C: \ powershell_scripts \ Get-SPN.ps1 e execute o seguinte no PowerShell:

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

(Obviamente, você pode salvar o script em outro local, basta atualizar a primeira linha conforme necessário.)

Isso listará todos os SPNs do SQL Server no domínio atual, incluindo a "especificação" relacionada à porta / instância do serviço.

Matt
fonte
Percebi que a maioria de nossas máquinas SQL Server não consegue obter SPNs (ou algum aviso desse tipo no log de manutenção). Eles ainda aparecerão usando esse script?
Elsimer 20/05
Isso geralmente ocorre porque o serviço é executado como um usuário que não é um administrador de domínio ou sistema local (necessário para criar o SPN). O administrador do domínio provavelmente adicionou SPNs usando o utilitário SetSPN e sua conta de administrador do domínio, para que a autenticação do domínio funcione corretamente para essas máquinas. Tão provável que sim.
Matt
0

Get-Service -ComputerName * MSSQL * | Onde-objeto {$ _. Status -eq "Em execução"}

Isso deve receber instâncias nomeadas e padrão. Apenas itere sua lista de máquinas, etc.

user41207
fonte
-4

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

Não foram retornados muitos dados, mas ele detectou todos os servidores sql que eu tenho em execução em um ambiente de VM.

Sean
fonte
6
Esse método já está incluído na resposta de Thomas Stringer .
MDCCL 28/11