¿Cuál es la forma más efectiva de descubrir todas las instancias en ejecución de SQL Server con PowerShell?


13

Me encargaron descubrir todas las instancias de SQL Server que se ejecutan dentro de nuestro dominio. En varios casos hay varias instancias por servidor. He visto dos métodos diferentes de PowerShell para encontrar estas instancias, pero ninguno parece encontrar todas las instancias.

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

El mayor problema con el que me encuentro es que no todos los servidores que conozco se ejecutan con el proveedor WMI de SQL Server ni todos permiten el registro remoto. ¿Hay un tercer método? Puedo usar Escritorio remoto para acceder a todos los servidores, pero estoy mirando aproximadamente 30 máquinas y me gustaría evitar los pasos manuales si es posible. Esto solo tiene que funcionar para SQL Server 2008 y versiones posteriores, y aunque sería bueno saber acerca de los otros servicios de SQL Server (SSIS / SSAS / SSRS), mi enfoque principal está en SQL Server.


Respuestas:


12

Si desea algo que sea útil para el futuro, probablemente evite intentar buscar en el registro. Las colmenas para SQL Server han cambiado un poco a lo largo de los años y puede ser problemático mantenerse al día.

El método con el SqlDataSourceEnumeratores escamoso a veces y aunque lo usaré, no hay evidencia concreta de que haya instancias en la red. Creo que también depende del servicio del navegador SQL, que la mayoría de las veces encuentro deshabilitado.

Utilizaré la clase WMI win32_Service. Lo uso porque ofrece más información sobre el servicio que el Get-Servicecmdlet.

Escribo todo como funciones en general porque puedes usar esto para hacer una verificación diaria o verificación del servicio para la resolución de 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
   }
 }
}

Esto es un poco más de lo que suelo usar, pero en caso de que alguien más se encuentre y quiera usarlo. Esto Test-Connectionequivale a ping myserverun indicador de DOS y la -Quietbandera simplemente lo devuelve trueo false. Esto establecerá de forma predeterminada 4 pings, por lo que la configuración -Count 2solo hace que lo haga dos veces.

La variable [string[]]$serveres un método utilizado para indicar que $serveraceptará una matriz de nombres de servidor. Entonces, una llamada de ejemplo de esta función podría verse así:

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

o

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

EDITAR

Un comentario destacado es que lo anterior depende de una lista de servidores que se proporciona. En los casos en que no se me proporcione esa lista, tiene algunas otras opciones.

  • Si estoy en un entorno de Active Directory, puedo usar el módulo ActiveDirectory en PowerShell para obtener una lista de todos los servidores en el dominio con Get-ADComputercmdlet. Sin embargo, una advertencia: asegúrese de usar un bien -Filteren dominios grandes.

  • También simplemente hice un escaneo IP (con aprobación) de una red que me da las direcciones IP donde se encontró el puerto 1433 abierto. Tomaré esa lista de IP y la utilizaré Get-ADComputerpara encontrar los nombres de computadora de dominio, luego la pasaré a la función anterior

Ejemplo:

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

La edición sugerida para utilizar Write-Verbosey también agregar en el bloque try / catch, aunque eso puede ser útil, y en la mayoría de los casos una práctica de código, lo dejaré en manos de la persona que quiera usar esta función para agregar ese código o funcionalidad adicional. Solo trato de proporcionar un ejemplo básico para continuar. Agregué la SystemNamepropiedad a la salida para incluir la información de retorno del nombre real del servidor, hacer esto en otras funciones, generalmente no lo uso para más de un servidor a la vez, así que se me olvidó.


Eso funciona siempre que se le proporcione una lista de servidores para empezar. Eso no siempre se puede suponer.
Thomas Stringer

Solo por claridad, limitar el escaneo al puerto 1433 dejará fuera cualquier servidor con solo instancias con nombre (o con instancias predeterminadas codificadas para usar un puerto diferente). Tal vez no sea un gran problema, pero hay muchas personas paranoicas que cierran ese puerto en toda la empresa.
Aaron Bertrand

Es cierto, es solo un punto de partida. En los que generalmente se configuran los puertos, he encontrado que los clientes suelen tener esos servidores anotados (conscientes de ellos). Encontré este método por Brian Kelley pero no lo he probado.

Creo que combinar el método de registro y el WMI win32_service como alternativa debería obtener la mayoría de los servidores, y luego funcionará una búsqueda manual del resto. Como efecto secundario agradable, que también se puede tirar un poco de información sobre los servicios que se están ejecutando, pero no son necesarios, los servidores que no están permitiendo el acceso de mí, etc
Elsimer

5

La única forma en que sé descubrir instancias en un entorno sin conocer todos los posibles servidores propietarios y sus nombres particulares, sería hacer una llamada a System.Data.Sql.SqlDataSourceEnumerator.GetDataSources (). Sin embargo, este método viene con muchas notas al pie. Aquí hay un fragmento que se extrae directamente de ese recurso de MSDN:

Debido a la naturaleza del mecanismo utilizado por SqlDataSourceEnumerator para ubicar las fuentes de datos en una red, el método no siempre devolverá una lista completa de los servidores disponibles , y la lista podría no ser la misma en cada llamada. Si planea utilizar esta función para permitir que los usuarios seleccionen un servidor de una lista, asegúrese de proporcionar siempre una opción para escribir un nombre que no esté en la lista, en caso de que la enumeración del servidor no devuelva todos los servidores disponibles . Además, este método puede tomar una cantidad considerable de tiempo en ejecutarse , así que tenga cuidado al llamarlo cuando el rendimiento es crítico.

La llamada es simple desde PowerShell:

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

Ese método devuelve un DataTableobjeto que puede manejar en consecuencia.


3

Si el servicio del navegador SQL está activo, puede consultar el servicio para instancias SQL con el código de PowerShell a continuación. Implementa los siguientes comandos para realizar las 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
        }
    }

2

Otra forma de identificar posibles instancias de SQL es mirar los nombres principales de servicio (SPN) enumerados en Active Directory. Cuando se conecta a SQL Server de forma remota con la autenticación de Windows, se utiliza un SPN en el proceso de autenticación. La presencia de un SPN no significa que el servidor / instancia definitivamente esté allí y ejecutándose, pero le brinda una lista de posibles instancias que he encontrado que son más completas que algunos de los otros enfoques.

Para facilitar la vida, uso el cmdlet Get-SPN de: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Descargue el script Get-SPN.ps1, guárdelo en C: \ powershell_scripts \ Get-SPN.ps1 y ejecute lo siguiente en PowerShell:

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

(Obviamente, puede guardar el script en otra ubicación, solo actualice la primera línea según sea necesario).

Esto enumerará todos los SPN de SQL Server en el dominio actual, incluida la "especificación" que se relaciona con el puerto / instancia del servicio.


Me di cuenta de que la mayoría de nuestras máquinas SQL Server no pueden obtener SPN (o alguna advertencia de este tipo en el registro de mantenimiento). ¿Seguirán apareciendo usando ese script?
Elsimer

Esto generalmente se debe a que el servicio se ejecuta como un usuario que no es un administrador de dominio o un sistema local (requerido para crear SPN). El administrador del dominio probablemente ha agregado SPN utilizando la utilidad SetSPN y su cuenta de administrador de dominio, por lo que la autenticación de dominio funciona correctamente para estas máquinas. Muy probablemente sí.
Matt

0

Get-Service -ComputerName * MSSQL * | Where-Object {$ _. Status -eq "En ejecución"}

Eso debería tener instancias con nombre y predeterminadas. Simplemente repita su lista de máquinas, etc.


-4

Acabo de intentar esto: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

No se devolvieron muchos datos, pero detectó todos los servidores sql que ejecuto en un entorno VM.


66
Ese método ya está incluido en la respuesta de Thomas Stringer .
MDCCL
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.