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 supérieures et même s'il serait bien de connaître les autres services SQL Server (SSIS/SSAS/SSRS), je me concentre principalement sur SQL Server lui-même.
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 SqlDataSourceEnumerator
est 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 aussi du service de navigateur SQL, que je trouve la plupart du temps désactivé.
J'utiliserai la classe WMI win32_Service
. J'utilise ceci car il offre plus d'informations sur le service que Get-Service
l'applet de commande le fait.
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 myserver
dans une invite DOS et le -Quiet
l'indicateur a simplement pour résultat true
ou false
. Ce sera par défaut à 4 pings, donc définissant -Count 2
le fait simplement le faire deux fois à la place.
La variable [string[]]$server
est une méthode utilisée pour indiquer que $server
acceptera 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
[~ # ~] modifier [~ # ~]
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-ADComputer
applet de commande. Un mot d'avertissement cependant assurez-vous d'utiliser un bon -Filter
sur les grands domaines.
J'ai également simplement effectué 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 utiliser Get-ADComputer
pour trouver les noms des ordinateurs du domaine, puis passez-les dans 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
[~ # ~] modifier [~ # ~]
La modification suggérée pour utiliser Write-Verbose
et également ajouter 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 propriété SystemName
à la sortie pour inclure le nom de 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 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 un 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 beaucoup de temps à 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 objet DataTable
que vous pouvez gérer en conséquence.
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-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
}
}
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 simplement à jour la première ligne si nécessaire.)
Cela répertoriera tous les SPN SQL Server sur le domaine actuel, y compris la "spécification" relative au port/à l'instance du service.
Get-Service -ComputerName * MSSQL * | Where-Object {$ _. Status -eq "Running"}
Cela devrait obtenir des instances nommées et par défaut. Itérez simplement votre liste de machines, etc.