使用PowerShell发现所有正在运行的SQL Server实例的最有效方法是什么?


13

我的任务是发现我们域中运行的所有SQL Server实例。在某些情况下,每个服务器有多个实例。我已经看到了找到这些实例的两种不同的PowerShell方法,但是似乎都找不到所有实例。

1)使用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)使用远程注册表(与Get-SQLInstance 1一样

我遇到的最大问题是,并不是我所知道的所有服务器都与SQL Server WMI提供程序一起运行,也不是它们都允许远程注册表。有第三种方法吗?我可以使用远程桌面访问所有服务器,但是我正在寻找大约30台计算机,如果可能的话,我希望避免手动操作。这仅适用于SQL Server 2008及更高版本,虽然很高兴了解其他SQL Server服务(SSIS / SSAS / SSRS),但我的主要重点是SQL Server本身。


Answers:


12

如果您想要对将来有用的东西,我可能会避免尝试搜索注册表。多年来,SQL Server的配置单元已经发生了一些变化,要跟上进度可能会很麻烦。

使用的方法SqlDataSourceEnumerator有时会很不稳定,尽管我会使用它,但没有具体证据表明实例在网络上。我相信这也取决于SQL Browser服务,在大多数情况下,我发现它已禁用。

我将利用WMI类win32_Service。我之所以使用它,是因为它比Get-Servicecmdlet 提供了更多有关服务的信息。

我通常将所有内容都写为函数,因为您可以使用它实际上只是每天检查或验证服务以进行故障排除。

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

这比我通常使用的要多,但是如果有人遇到想要使用它的话。在Test-Connection以相当于ping myserver在DOS提示符和-Quiet标志只是简单地拥有它返回truefalse。这将默认为4 ping,因此设置-Count 2只会使其执行两次。

该变量[string[]]$server是一种用于声明$server将接受服务器名称数组的方法。因此,此函数的示例调用可能类似于:

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

要么

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

编辑

上面提到的注释确实取决于提供的服务器列表。如果没有提供该列表,则您还有其他选择。

  • 如果我处于Active Directory环境中,则可以使用PowerShell中的ActiveDirectory模块通过cmdlet提取域中所有服务器的列表Get-ADComputer。请注意,不过请确保-Filter在大型域上使用良好。

  • 我还简单地对网络进行了IP扫描(获得批准),该扫描为我提供了发现端口1433打开的IP地址。我将获取该IP列表并利用它Get-ADComputer来查找域计算机名称,然后将其传递给上面的函数

例:

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

编辑

建议使用Write-Verbose并添加try / catch块的编辑方法,尽管这可能是有用的,但在大多数情况下,这是一种代码实践,我将留给希望使用此功能来添加该额外代码或功能的人员。只是尝试提供一个基本的例子。我确实SystemName在输出中添加了该属性,以包括返回实际服务器名称的信息,但在其他功能上执行此操作通常只是一次不要将其用于一个以上的服务器,所以我不介意。


只要提供了一系列服务器列表,该方法就可以使用。不能总是这样假设。
Thomas Stringer

只是为了清楚起见,将扫描限制在端口1433会忽略所有仅具有命名实例的服务器(或具有硬编码为使用其他端口的默认实例)。也许没什么大不了,但是那里有很多偏执狂的人在整个企业范围内关闭了该港口。
亚伦·伯特兰

是的,这只是一个起点。通常我设置了端口的那些我发现客户端通常会注意到那些服务器(意识到它们)。确实找到了Brian Kelley的这种方法,但是还没有尝试过。

我认为结合注册表方法和win32_service WMI作为后备应该可以获取大多数服务器,然后手动搜索其余服务器即可。作为一个愉快的副作用,我也可以拉在运行,但并不需要那些不允许我访问等服务,服务器的一些信息
Elsimer

5

我知道在整个环境中发现实例而不知道所有可能拥有的服务器及其特定名称的唯一方法是调用System.Data.Sql.SqlDataSourceEnumerator.GetDataSources()。 但是,此方法附带很多脚注。以下是直接从该MSDN资源中提取的代码段:

由于SqlDataSourceEnumerator用于在网络上定位数据源的机制的性质,该方法将不会始终返回 可用服务器完整列表,并且每次调用时该列表可能都不相同。如果您打算使用此功能让用户从列表中选择服务器,请确保您始终还提供一个选项来键入不在列表中的名称,以防服务器枚举未返回所有可用服务器的情况。此外, 此方法可能需要花费大量时间才能执行,因此在性能至关重要时,请谨慎调用它。

调用从PowerShell很简单:

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

该方法返回一个DataTable可以相应处理的对象。


3

如果SQL浏览器服务处于活动状态,则可以使用下面的PowerShell代码在该服务中查询SQL实例。它实现了以下命令行开关来执行查询:

  • 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

识别可能的SQL实例的另一种方法是查看Active Directory中列出的服务主体名称(SPN)。当您使用Windows身份验证远程连接到SQL Server时,在身份验证过程中将使用SPN。SPN的存在并不意味着服务器/实例肯定已经存在并且正在运行,但是它确实为您提供了可能的实例列表,我发现其中一些其他方法更为全面。

为了使生活更轻松,我使用以下网址中的Get-SPN cmdlet:https//gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

下载Get-SPN.ps1脚本,将其保存到C:\ powershell_scripts \ Get-SPN.ps1并在PowerShell中运行以下命令:

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

(显然,您可以将脚本保存到其他位置,只需根据需要更新第一行。)

这将列出当前域上的所有SQL Server SPN,包括与服务的端口/实例有关的“规范”。


我注意到我们的大多数SQL Server计算机都无法获取SPN(或维护日志中的某些此类警告)。他们还会使用该脚本显示吗?
Elsimer

这通常是因为该服务以不是域管理员或本地系统(创建SPN所必需)的用户身份运行。域管理员可能已使用SetSPN实用程序及其域管理员帐户添加了SPN,因此域身份验证可在这些计算机上正常运行。很可能,是的。
马特2015年

0

Get-Service-计算机名* MSSQL * | 哪里对象{$ _。status -eq“正在运行”}

那应该得到命名实例和默认实例。只需迭代您的计算机列表等即可。


-4

刚刚尝试过:[System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources()

返回的数据不是很多,但是它确实检测到了我在VM环境中运行的所有sql服务器。


6
该方法已经包含在Thomas Stringer的答案中
MDCCL
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.