Was ist die effektivste Methode, um alle ausgeführten Instanzen von SQL Server mithilfe von PowerShell zu ermitteln?

13

Ich wurde beauftragt, alle Instanzen von SQL Server zu ermitteln, die in unserer Domäne ausgeführt werden. In mehreren Fällen gibt es mehrere Instanzen pro Server. Ich habe zwei verschiedene PowerShell-Methoden zum Finden dieser Instanzen gesehen, aber keine scheint alle Instanzen zu finden.

1) Verwenden Sie 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) Verwenden Sie die Remoteregistrierung (wie bei Get-SQLInstance 1 ).

Das größte Problem, auf das ich stoße, ist, dass nicht alle Server, die ich kenne, mit dem SQL Server-WMI-Anbieter ausgeführt werden und auch keine Remoteregistrierung zulassen. Gibt es eine dritte Methode? Ich kann mit Remotedesktop auf alle Server zugreifen, aber ich sehe ungefähr 30 Computer und möchte manuelle Schritte nach Möglichkeit vermeiden. Dies muss nur für SQL Server 2008 und höher funktionieren, und während es nett wäre, über die anderen SQL Server-Dienste (SSIS / SSAS / SSRS) Bescheid zu wissen, ist mein Hauptaugenmerk auf SQL Server selbst gerichtet.

Elsimer
quelle

Antworten:

12

Wenn Sie etwas wollen, das für die Zukunft nützlich sein wird, würde ich wahrscheinlich vermeiden, die Registrierung zu durchsuchen. Die Strukturen für SQL Server haben sich im Laufe der Jahre etwas geändert und es kann schwierig sein, mitzuhalten.

Die Methode mit dem SqlDataSourceEnumeratorist manchmal flockig und obwohl ich sie verwenden werde, kein konkreter Beweis dafür, dass Instanzen im Netzwerk sind. Ich glaube, es hängt auch vom SQL Browser-Dienst ab, den ich die meiste Zeit für deaktiviert halte.

Ich werde die WMI-Klasse verwenden win32_Service. Ich benutze dies, weil es mehr Informationen über den Dienst bietet alsGet-Service Cmdlet.

Ich schreibe im Allgemeinen alles als Funktionen, weil Sie dies verwenden können, um tatsächlich nur eine tägliche Überprüfung oder Verifizierung des Dienstes zur Fehlerbehebung durchzuführen.

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

Dies ist etwas mehr als das, was ich normalerweise benutze, aber für den Fall, dass jemand anderes darauf stößt und es verwenden möchte. Das Test-Connectionentspricht ping myserverin einer DOS-Eingabeaufforderung und das -QuietFlag hat es einfach zurück trueoder false. Dies wird standardmäßig auf 4 Pings eingestellt, so -Count 2dass es bei der Einstellung stattdessen nur zweimal ausgeführt wird.

Die Variable [string[]]$serverist eine Methode, mit der angegeben $serverwird, dass ein Array von Servernamen akzeptiert wird. Ein Beispielaufruf dieser Funktion könnte also so aussehen:

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

oder

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

BEARBEITEN

Ein Hinweis ist, dass der obige Hinweis von einer Liste der bereitgestellten Server abhängt. In Fällen, in denen mir diese Liste nicht zur Verfügung gestellt wird, haben Sie einige andere Möglichkeiten.

  • Wenn ich mich in einer Active Directory-Umgebung befinde, kann ich mithilfe des ActiveDirectory- Moduls in PowerShell eine Liste aller Server in der Domäne mit dem Get-ADComputerCmdlet abrufen. Ein Wort der Warnung, stellen Sie jedoch sicher, dass Sie eine gute -Filterauf großen Domänen verwenden.

  • Ich habe auch einfach einen IP-Scan (mit Genehmigung) eines Netzwerks durchgeführt, der mir die IP-Adressen angibt, bei denen Port 1433 als offen befunden wurde. Ich werde diese IP-Liste verwenden Get-ADComputer, um die Domänencomputernamen zu finden, und diese dann an die obige Funktion übergeben

Beispiel:

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

BEARBEITEN

Die vorgeschlagene Bearbeitung, um den Write-VerboseTry / Catch-Block zu verwenden und auch hinzuzufügen, während dies nützlich sein kann und in den meisten Fällen eine Code-Übung ist, überlasse ich es der Person, die diese Funktion verwenden möchte, um diesen zusätzlichen Code oder diese zusätzliche Funktionalität hinzuzufügen. Ich versuche nur, ein einfaches Beispiel zu liefern, um fortzufahren. Ich habe die SystemNameEigenschaft zur Ausgabe hinzugefügt , um den tatsächlichen Servernamen, der Informationen zurückgibt, einzuschließen. Tun Sie dies für andere Funktionen und verwenden Sie dies im Allgemeinen nicht für mehr als einen Server gleichzeitig.


quelle
Dies funktioniert, vorausgesetzt, Sie erhalten zunächst eine Liste der Server. Das ist nicht immer anzunehmen.
Thomas Stringer
Nur aus Gründen der Übersichtlichkeit lässt die Beschränkung des Scanvorgangs auf Port 1433 Server mit nur benannten Instanzen (oder mit Standardinstanzen, die für die Verwendung eines anderen Ports fest codiert sind) aus. Vielleicht keine große Sache, aber es gibt viele paranoide Leute, die diesen Hafen unternehmensweit schließen.
Aaron Bertrand
Es ist wahr, es ist nur ein Ausgangspunkt. Bei denjenigen, bei denen Ports im Allgemeinen festgelegt sind, habe ich festgestellt, dass Clients normalerweise diese Server notiert haben (wenn ich sie kenne). Habe diese Methode von Brian Kelley gefunden, aber noch nicht ausprobiert.
Ich denke, das Kombinieren der Registrierungsmethode und des win32_service WMI als Fallback sollte die Mehrheit der Server erreichen, und dann wird eine manuelle Suche des Rests funktionieren. Als angenehmer Nebeneffekt kann ich auch Informationen zu Diensten
abrufen
5

Die einzige Möglichkeit, Instanzen in einer Umgebung zu erkennen, ohne alle möglichen Server mit ihren jeweiligen Namen zu kennen, besteht darin, System.Data.Sql.SqlDataSourceEnumerator.GetDataSources () aufzurufen . Diese Methode wird jedoch mit vielen Fußnoten geliefert. Hier ist ein Snippet, das direkt von dieser MSDN-Ressource abgerufen wird:

Aufgrund der Art des von SqlDataSourceEnumerator zum Suchen von Datenquellen in einem Netzwerk verwendeten Mechanismus gibt die Methode nicht immer eine vollständige Liste der verfügbaren Server zurück , und die Liste ist möglicherweise nicht bei jedem Aufruf dieselbe. Wenn Sie diese Funktion verwenden möchten, damit Benutzer einen Server aus einer Liste auswählen können, stellen Sie sicher, dass Sie immer auch die Option angeben, einen Namen einzugeben, der nicht in der Liste enthalten ist, falls die Server-Aufzählung nicht alle verfügbaren Server zurückgibt . Darüber hinaus kann die Ausführung dieser Methode einige Zeit in Anspruch nehmen. Seien Sie daher vorsichtig, wenn Sie sie aufrufen, wenn die Leistung kritisch ist.

Der Aufruf von PowerShell ist einfach:

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

Diese Methode gibt ein DataTableObjekt zurück, das Sie entsprechend behandeln können.

Thomas Stringer
quelle
3

Wenn der SQL-Browserdienst aktiv ist, können Sie den Dienst mit dem folgenden PowerShell-Code nach SQL-Instanzen abfragen. Es implementiert die folgenden Kommandos, um die Abfragen auszuführen:

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

Eine andere Möglichkeit, mögliche SQL-Instanzen zu identifizieren, besteht darin, die in Active Directory aufgelisteten Dienstprinzipalnamen (Service Principle Names, SPNs) zu überprüfen. Wenn Sie mit Windows-Authentifizierung eine Remoteverbindung zu SQL Server herstellen, wird bei der Authentifizierung ein SPN verwendet. Das Vorhandensein eines SPN bedeutet nicht, dass der Server / die Instanz definitiv vorhanden ist und ausgeführt wird, aber es gibt Ihnen eine Liste möglicher Instanzen, die ich im Vergleich zu einigen anderen Ansätzen für umfassender befunden habe.

Um das Leben zu vereinfachen, verwende ich das Cmdlet Get-SPN von: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Laden Sie das Skript Get-SPN.ps1 herunter, speichern Sie es in C: \ powershell_scripts \ Get-SPN.ps1 und führen Sie in PowerShell Folgendes aus:

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

(Natürlich können Sie das Skript auch an einem anderen Ort speichern. Aktualisieren Sie einfach die erste Zeile nach Bedarf.)

Dadurch werden alle SQL Server-SPNs in der aktuellen Domäne aufgelistet, einschließlich der "Spezifikation", die sich auf den Port / die Instanz des Dienstes bezieht.

Matt
quelle
Ich habe festgestellt, dass die meisten unserer SQL Server-Computer keine SPNs abrufen können (oder eine solche Warnung im Wartungsprotokoll). Werden sie immer noch mit diesem Skript auftauchen?
Elsimer
Dies liegt in der Regel daran, dass der Dienst als Benutzer ausgeführt wird, der kein Domänenadministrator oder lokales System ist (zum Erstellen eines SPN erforderlich). Der Domänenadministrator hat wahrscheinlich mithilfe des Dienstprogramms SetSPN und seines Domänenadministratorkontos SPNs hinzugefügt, sodass die Domänenauthentifizierung für diese Computer ordnungsgemäß funktioniert. So wahrscheinlich, ja.
Matt
0

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

Das sollte benannt werden und Standardinstanzen. Wiederholen Sie einfach Ihre Maschinenliste usw.

user41207
quelle
-4

Ich habe es gerade versucht: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Es wurden nicht viele Daten zurückgegeben, aber es wurden alle SQL-Server erkannt, die ich in einer VM-Umgebung ausgeführt habe.

Sean
quelle
6
Diese Methode ist bereits in der Antwort von Thomas Stringer enthalten .
MDCCL