Quelle est la façon la plus efficace de découvrir toutes les instances de SQL Server en cours d'exécution à l'aide de PowerShell?

13

J'ai été chargé de découvrir toutes les instances de SQL Server qui s'exécutent dans notre domaine. Dans plusieurs cas, il existe plusieurs instances par serveur. J'ai vu deux méthodes PowerShell différentes pour trouver ces instances, mais aucune ne semble trouver toutes les instances.

1) Utilisez 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) Utilisez un registre distant (comme avec Get-SQLInstance 1 )

Le plus gros problème que je rencontre est que tous les serveurs que je connais ne fonctionnent pas avec le fournisseur SQL Server WMI et n'autorisent pas tous le registre distant. Existe-t-il une troisième méthode? Je peux utiliser Remote Desktop pour accéder à tous les serveurs mais je regarde environ 30 machines et j'aimerais éviter les étapes manuelles si possible. Cela ne doit fonctionner que pour SQL Server 2008 et versions ultérieures et même s'il serait bien de connaître les autres services SQL Server (SSIS / SSAS / SSRS), mon objectif principal est SQL Server lui-même.

Elsimer
la source

Réponses:

12

Si vous voulez quelque chose qui sera utile pour l'avenir, je me garderais probablement d'essayer de rechercher dans le registre. Les ruches pour SQL Server ont un peu changé au fil des ans et il peut être difficile de suivre.

La méthode avec le SqlDataSourceEnumeratorest parfois instable et bien que je l'utilise, ce n'est pas une preuve concrète que des instances sont sur le réseau. Je crois que cela dépend également du service de navigateur SQL, que je trouve la plupart du temps désactivé.

J'utiliserai la classe WMI win32_Service. J'utilise cela car il offre plus d'informations sur le service que la Get-Servicecmdlet.

J'écris tout en tant que fonctions en général parce que vous pouvez l'utiliser pour faire juste une vérification ou une vérification quotidienne du service pour le dépannage.

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

C'est un peu plus que ce que j'utilise habituellement, mais au cas où quelqu'un d'autre viendrait et voudrait l'utiliser. Le Test-Connectionéquivaut à ping myserverdans une invite DOS et le -Quietdrapeau l'a simplement renvoyé trueou false. Ce sera par défaut à 4 pings donc le réglage le -Count 2fait simplement le faire deux fois à la place.

La variable [string[]]$serverest une méthode utilisée pour déclarer qui $serveracceptera un tableau de noms de serveur. Ainsi, un exemple d'appel de cette fonction pourrait ressembler à ceci:

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

ou

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

ÉDITER

Un commentaire noté est que ce qui précède dépend d'une liste de serveurs fournie. Dans les cas où je ne reçois pas cette liste, vous avez quelques autres options.

  • Si je suis dans un environnement Active Directory, je peux utiliser le module ActiveDirectory dans PowerShell pour extraire une liste de tous les serveurs du domaine avec Get-ADComputercmdlet. Un mot d'avertissement cependant assurez-vous d'utiliser un bien -Filtersur de grands domaines.

  • J'ai également simplement fait une analyse IP (avec approbation) d'un réseau qui me donne les adresses IP où le port 1433 a été trouvé ouvert. Je vais prendre cette liste IP et l'utiliser Get-ADComputerpour trouver les noms des ordinateurs du domaine, puis les transmettre à la fonction ci-dessus

Exemple:

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

ÉDITER

La modification suggérée à utiliser Write-Verboseet à ajouter également un bloc try / catch, bien que cela puisse être utile, et dans la plupart des cas une pratique de code, je laisserai cela à la personne qui veut utiliser cette fonction pour ajouter ce code ou cette fonctionnalité supplémentaire. J'essaie juste de fournir un exemple de base pour continuer. J'ai ajouté la SystemNamepropriété à la sortie pour inclure le nom du serveur réel renvoyant des informations, faites-le sur d'autres fonctions, ne l'utilisez généralement pas pour plus d'un serveur à la fois, donc cela m'a glissé.


la source
Cela fonctionne à condition que vous soyez fourni avec une liste de serveurs pour commencer. Cela ne peut pas toujours être supposé.
Thomas Stringer
Par souci de clarté, la limitation de l'analyse au port 1433 laissera de côté tous les serveurs avec uniquement des instances nommées (ou avec des instances par défaut codées en dur pour utiliser un port différent). Ce n'est peut-être pas un gros problème, mais il y a beaucoup de gens paranoïaques qui ont fermé ce port à l'échelle de l'entreprise.
Aaron Bertrand
Certes, ce n'est qu'un point de départ. Ceux où les ports sont généralement définis, j'ai trouvé des clients ont généralement ces serveurs notés (conscients d'eux). J'ai trouvé cette méthode par Brian Kelley mais je ne l'ai pas essayée.
Je pense que la combinaison de la méthode de registre et du WMI win32_service en tant que solution de repli devrait obtenir la majorité des serveurs, puis une recherche manuelle du reste fonctionnera. Comme effet secondaire agréable, je peux également extraire des informations sur les services qui sont en cours d'exécution mais qui ne sont pas nécessaires, les serveurs qui ne me permettent pas d'accéder, etc.
Elsimer
5

La seule façon que je connaisse de découvrir des instances dans un environnement sans connaître tous les serveurs propriétaires possibles et leurs noms particuliers serait d'appeler System.Data.Sql.SqlDataSourceEnumerator.GetDataSources (). Cette méthode est cependant accompagnée de nombreuses notes de bas de page. Voici un extrait extrait directement de cette ressource MSDN:

En raison de la nature du mécanisme utilisé par SqlDataSourceEnumerator pour localiser les sources de données sur un réseau, la méthode ne renverra pas toujours une liste complète des serveurs disponibles et la liste peut ne pas être la même à chaque appel. Si vous prévoyez d'utiliser cette fonction pour permettre aux utilisateurs de sélectionner un serveur dans une liste, assurez-vous de toujours fournir une option pour taper un nom qui ne figure pas dans la liste, au cas où l'énumération du serveur ne renvoie pas tous les serveurs disponibles . De plus, cette méthode peut prendre un temps considérable à exécuter , donc faites attention à l'appeler lorsque les performances sont critiques.

L'appel est simple depuis PowerShell:

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

Cette méthode renvoie un DataTable objet que vous pouvez gérer en conséquence.

Thomas Stringer
la source
3

Si le service de navigateur SQL est actif, vous pouvez interroger le service pour les instances SQL avec le code PowerShell ci-dessous. Il implémente les commandlets suivants pour effectuer les requêtes:

  • 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
la source
2

Une autre façon d'identifier les instances SQL possibles consiste à examiner les noms de principe de service (SPN) répertoriés dans Active Directory. Lorsque vous vous connectez à SQL Server à distance avec l'authentification Windows, un SPN est utilisé dans le processus d'authentification. La présence d'un SPN ne signifie pas que le serveur / l'instance est définitivement là et fonctionne mais cela vous donne une liste d'instances possibles que j'ai trouvées plus complètes que certaines des autres approches.

Pour vous faciliter la vie, j'utilise l'applet de commande Get-SPN à partir de: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Téléchargez le script Get-SPN.ps1, enregistrez-le dans C: \ powershell_scripts \ Get-SPN.ps1 et exécutez ce qui suit dans PowerShell:

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

(Évidemment, vous pouvez enregistrer le script dans un autre emplacement, mettez à jour la première ligne au besoin.)

Cela répertoriera tous les SPN SQL Server sur le domaine actuel, y compris la "spécification" relative au port / à l'instance du service.

Mat
la source
J'ai remarqué que la plupart de nos machines SQL Server ne peuvent pas obtenir de SPN (ou un avertissement de ce type dans le journal de maintenance). Vont-ils toujours apparaître en utilisant ce script?
Elsimer
C'est généralement parce que le service s'exécute en tant qu'utilisateur qui n'est pas un administrateur de domaine ou un système local (requis pour créer un SPN). L'administrateur de domaine a probablement ajouté des SPN à l'aide de l'utilitaire SetSPN et de son compte d'administrateur de domaine, afin que l'authentification de domaine fonctionne correctement pour ces machines. Si probable, oui.
Matt
0

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

Cela devrait obtenir des instances nommées et par défaut. Parcourez simplement votre liste de machines, etc.

user41207
la source
-4

Je viens d'essayer ceci: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Pas beaucoup de données retournées, mais il a détecté tous les serveurs SQL que j'exécute dans un environnement de machine virtuelle.

Sean
la source
6
Cette méthode est déjà incluse dans la réponse de Thomas Stringer .
MDCCL