Qual è il modo più efficace per scoprire tutte le istanze in esecuzione di SQL Server usando PowerShell?


13

Mi è stato assegnato il compito di scoprire tutte le istanze di SQL Server in esecuzione nel nostro dominio. In diversi casi ci sono più istanze per server. Ho visto due diversi metodi di PowerShell per trovare queste istanze, ma nessuno dei due sembra trovare tutte le istanze.

1) Usa 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) Usa registro remoto (come con Get-SQLInstance 1 )

Il problema più grande in cui mi imbatto è che non tutti i server che conosco sono in esecuzione con il provider WMI di SQL Server né tutti consentono il registro remoto. C'è un terzo metodo? Posso usare Remote Desktop per accedere a tutti i server ma sto guardando circa 30 macchine e vorrei evitare passaggi manuali, se possibile. Questo deve funzionare solo per SQL Server 2008 e versioni successive e mentre sarebbe bello sapere degli altri servizi di SQL Server (SSIS / SSAS / SSRS) il mio obiettivo principale è su SQL Server stesso.


Risposte:


12

Se si desidera qualcosa che sarà utile per il futuro, probabilmente mi starei alla larga dal tentativo di cercare nel registro. Gli alveari per SQL Server sono cambiati un po 'nel corso degli anni e può essere problematico tenere il passo.

Il metodo con the SqlDataSourceEnumeratora volte è traballante e anche se lo userò, non prove concrete che le istanze siano sulla rete. Credo che dipenda anche dal servizio SQL Browser, che la maggior parte delle volte trovo disabilitato.

Userò la classe WMI win32_Service. Lo uso perché offre più informazioni sul servizio rispetto al Get-Servicecmdlet.

Scrivo tutto come funzioni in generale perché puoi usarlo per fare semplicemente il controllo o la verifica giornaliera del servizio per la risoluzione dei problemi.

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

Questo è un po 'più di quello che di solito uso, ma nel caso in cui qualcuno si imbatte e vuole usarlo. Le Test-Connectionè assimilato a ping myserverun prompt di DOS e la -Quietbandiera semplicemente deve solo si restituiscono trueo false. L'impostazione predefinita è 4 ping, quindi l'impostazione -Count 2consente di farlo invece due volte.

La variabile [string[]]$serverè un metodo utilizzato per affermare che $serveraccetterà una matrice di nomi di server. Quindi un esempio di chiamata di questa funzione potrebbe assomigliare a:

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

o

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

MODIFICARE

Un commento noto è che quanto sopra dipende da un elenco di server forniti. Nei casi in cui non mi viene fornito tale elenco, hai alcune altre opzioni.

  • Se mi trovo in un ambiente Active Directory, posso usare il modulo ActiveDirectory in PowerShell per estrarre un elenco di tutti i server sul dominio con il Get-ADComputercmdlet. Un avvertimento, tuttavia, assicurati di utilizzare un buon -Filtersu grandi domini.

  • Ho anche semplicemente fatto una scansione IP (con approvazione) di una rete che mi dà gli indirizzi IP in cui la porta 1433 è stata trovata aperta. Prenderò quell'elenco IP e lo userò Get-ADComputerper trovare i nomi dei computer di dominio, quindi lo passerò nella funzione sopra

Esempio:

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

MODIFICARE

La modifica suggerita da utilizzare Write-Verbosee anche aggiungere nel blocco try / catch, mentre ciò può essere utile, e nella maggior parte dei casi una pratica del codice, la lascerò alla persona che desidera utilizzare questa funzione per aggiungere quel codice o funzionalità aggiuntiva. Sto solo cercando di fornire un esempio di base per continuare. Ho aggiunto la SystemNameproprietà all'output per includere il nome del server effettivo che restituisce informazioni, farlo su altre funzioni in genere non lo uso per più di un server alla volta, quindi mi è passato di mente.


Funziona a condizione che ti venga fornito un elenco di server per cominciare. Non si può sempre presumere.
Thomas Stringer,

Solo per chiarezza, limitando la scansione alla porta 1433 si tralascerà qualsiasi server con solo istanze denominate (o con istanze predefinite codificate per usare una porta diversa). Forse non è un grosso problema, ma ci sono molte persone paranoiche là fuori che chiudono quel porto a livello aziendale.
Aaron Bertrand

È vero, è solo un punto di partenza. Quelli in cui le porte sono generalmente impostate Ho scoperto che i client di solito hanno notato quei server (a conoscenza di essi). Ho trovato questo metodo di Brian Kelley ma non l'ho provato.

Penso che combinando sia il metodo di registro che il WMI win32_service come fallback dovrebbe ottenere la maggior parte dei server, e quindi una ricerca manuale del resto funzionerà. Come piacevole effetto collaterale, posso anche ottenere alcune informazioni sui servizi in esecuzione ma non necessari, sui server che non mi consentono l'accesso, ecc.
Elsimer,

5

L'unico modo che conosco per scoprire istanze in un ambiente senza conoscere tutti i possibili server proprietari e i loro nomi particolari sarebbe quello di effettuare una chiamata a System.Data.Sql.SqlDataSourceEnumerator.GetDataSources (). Questo metodo viene fornito con molte note a piè di pagina, tuttavia. Ecco uno snippet che viene estratto direttamente da quella risorsa MSDN:

A causa della natura del meccanismo utilizzato da SqlDataSourceEnumerator per individuare le origini dati su una rete, il metodo non restituirà sempre un elenco completo dei server disponibili e l'elenco potrebbe non essere lo stesso ad ogni chiamata. Se si prevede di utilizzare questa funzione per consentire agli utenti di selezionare un server da un elenco, assicurarsi di fornire sempre un'opzione per digitare un nome che non è nell'elenco, nel caso in cui l'enumerazione del server non restituisca tutti i server disponibili . Inoltre, l' esecuzione di questo metodo può richiedere molto tempo , quindi fai attenzione a chiamarlo quando le prestazioni sono fondamentali.

La chiamata è semplice da PowerShell:

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

Tale metodo restituisce un DataTableoggetto che è possibile gestire di conseguenza.


3

Se il servizio browser SQL è attivo, è possibile eseguire una query sul servizio per istanze SQL con il codice PowerShell riportato di seguito. Implementa i seguenti comandi per eseguire le query:

  • 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

Un altro modo per identificare possibili istanze SQL è quello di esaminare i nomi dei principi di servizio (SPN) elencati in Active Directory. Quando ci si connette a SQL Server in remoto con l'autenticazione di Windows, viene utilizzato un SPN nel processo di autenticazione. La presenza di un SPN non significa che il server / istanza sia sicuramente presente e funzionante, ma ti dà un elenco di possibili istanze che ho trovato più complete rispetto ad alcuni degli altri approcci.

Per semplificare la vita, utilizzo il cmdlet Get-SPN da: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Scarica lo script Get-SPN.ps1, salvalo in C: \ powershell_scripts \ Get-SPN.ps1 ed esegui quanto segue in PowerShell:

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

(Ovviamente puoi salvare lo script in un'altra posizione, basta aggiornare la prima riga come richiesto.)

Verranno elencati tutti gli SPN di SQL Server nel dominio corrente, inclusa la "specifica" relativa alla porta / istanza del servizio.


Ho notato che la maggior parte delle nostre macchine SQL Server non è in grado di ottenere SPN (o alcuni avvisi di questo tipo nel registro di manutenzione). Verranno comunque mostrati usando quello script?
Elsimer,

Questo di solito perché il servizio viene eseguito come un utente che non è un amministratore di dominio o un sistema locale (richiesto per creare SPN). L'amministratore del dominio ha probabilmente aggiunto SPN usando l'utilità SetSPN e il suo account di amministratore del dominio, in modo che l'autent di dominio funzioni correttamente per queste macchine. Quindi probabilmente sì.
Matt,

0

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

Questo dovrebbe avere istanze nominate e predefinite. Basta scorrere il tuo elenco di macchine, ecc.


-4

Ho appena provato questo: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Non sono stati restituiti molti dati, ma sono stati rilevati tutti i server sql che ho in esecuzione in un ambiente VM.


6
Tale metodo è già incluso nella risposta di Thomas Stringer .
MDCCL,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.