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 자체에 중점을 둡니다.


답변:


12

미래에 유용한 것을 원한다면 레지스트리를 검색하려고 시도하지 않을 것입니다. SQL Server의 두드러기는 수년에 걸쳐 약간 변경되어 따라 가기가 어려울 수 있습니다.

를 사용하는 방법 SqlDataSourceEnumerator은 때때로 색다른 방법이며 사용하지만 인스턴스가 네트워크에 있다는 구체적인 증거는 아닙니다. 나는 그것이 SQL 브라우저 서비스에도 의존한다고 믿습니다.

나는 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도스 프롬프트를에 -Quiet플래그가 단순히 그것을 가지고 반환 true또는 false. 기본적으로 4 개의 핑이 설정되므로 설정 -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 모듈을 사용하여 Get-ADComputercmdlet 을 사용하여 도메인의 모든 서버 목록을 가져올 수 있습니다 . 경고는 한마디로 -Filter큰 도메인 에서 좋은 제품 을 사용해야 합니다.

  • 또한 포트 1433이 열려있는 IP 주소를 제공하는 네트워크의 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-Verbosetry / catch 블록 을 활용 하고 추가 하기 위해 제안 된 편집 은 유용 할 수 있지만 대부분의 경우 코드 연습에서는이 기능을 사용하여 추가 코드 또는 기능을 추가하려는 사람에게 맡길 것입니다. 계속할 기본 예제를 제공하려고합니다. SystemName실제 서버 이름 반환 정보를 포함하기 위해 출력에 속성을 추가했습니다 . 다른 기능 에서이 작업을 수행하면 일반적으로 한 번에 두 대 이상의 서버 에서이 기능을 사용하지 않으므로 마음이 미끄러졌습니다.


시작하는 서버 목록이 제공되면 작동합니다. 항상 가정 할 수는 없습니다.
Thomas Stringer

명확성을 위해 스캔을 포트 1433으로 제한하면 명명 된 인스턴스 만있는 서버 (또는 다른 포트를 사용하도록 하드 코딩 된 기본 인스턴스가있는 서버)가 제외됩니다. 어쩌면 큰 문제는 아니지만 전사적으로 그 항구를 폐쇄 한 편집증 사람들이 많이 있습니다.
Aaron Bertrand

사실, 그것은 시작에 불과합니다. 포트가 일반적으로 설정되어있는 서버에는 클라이언트가 일반적으로 해당 서버가 있음을 알았습니다. Brian Kelley 가이 방법을 찾았 지만 시도하지 않았습니다.

대체 방법으로 레지스트리 방법과 win32_service WMI를 결합하면 대부분의 서버를 가져와 나머지를 수동으로 검색 할 수 있다고 생각합니다. 유쾌한 부작용으로, 실행 중이지만 필요하지 않은 서비스, 액세스 할 수없는 서버 등에 대한 정보를 가져올 수도 있습니다.
Elsimer

5

가능한 모든 소유 서버 및 해당 특정 이름을 모른 채 환경에서 인스턴스를 발견하는 유일한 방법은 System.Data.Sql.SqlDataSourceEnumerator.GetDataSources ()를 호출하는 것 입니다. 그러나이 방법에는 많은 각주가 있습니다. 다음은 해당 MSDN 리소스에서 직접 가져온 스 니펫입니다.

SqlDataSourceEnumerator가 네트워크에서 데이터 소스를 찾기 위해 사용하는 메커니즘의 특성으로 인해이 메소드는 항상 사용 가능한 서버 전체 목록을 리턴 하지는 않으며 모든 호출에서 목록이 동일하지 않을 수도 있습니다. 이 기능을 사용하여 사용자가 목록에서 서버를 선택할 수있게하려면 서버 열거에서 사용 가능한 모든 서버를 반환하지 않는 경우 목록에없는 이름을 입력하는 옵션도 항상 제공해야합니다 . 또한 이 메소드는 실행하는 데 상당한 시간이 걸리므로 성능이 중요 할 때 호출하는 데주의하십시오.

PowerShell에서 간단하게 호출 할 수 있습니다.

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

이 메소드는 DataTable그에 따라 처리 할 수 있는 객체를 반환합니다 .


3

SQL Browser 서비스가 활성화 된 경우 아래 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 (Service Principle Names)을 보는 것입니다. Windows 인증을 사용하여 SQL Server에 원격으로 연결하면 SPN이 인증 프로세스에 사용됩니다. SPN이 있다고해서 서버 / 인스턴스가 확실히 존재하고 실행중인 것은 아니지만 다른 접근 방식 중 일부에 대해 더 포괄적 인 것으로 확인 된 가능한 인스턴스 목록을 제공합니다.

삶을 편하게하기 위해 https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a 에서 Get-SPN cmdlet을 사용합니다.

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을 추가 했으므로 도메인 인증이 이러한 시스템에 제대로 작동합니다. 그렇습니다.
Matt

0

Get-Service -ComputerName * MSSQL * | Where-Object {$ _. status -eq "실행 중"}

명명 된 기본 인스턴스가 있어야합니다. 기계 목록 등을 반복하십시오.


-4

그냥 이것을 시도 : [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

많은 데이터가 반환되지는 않지만 VM 환경에서 실행중인 모든 SQL Server를 감지했습니다.


6
이 방법은 이미 Thomas Stringer의 답변에 포함되어 있습니다.
MDCCL
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.