PowerShellを使用してSQL Serverの実行中のすべてのインスタンスを検出する最も効果的な方法は何ですか?


13

私は、ドメイン内で実行されているSQL Serverのすべてのインスタンスを検出するタスクを担当しました。いくつかの場合、サーバーごとに複数のインスタンスがあります。これらのインスタンスを見つける2つの異なる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プロバイダーで実行されておらず、すべてがリモートレジストリを許可していないことです。3番目の方法はありますか?リモートデスクトップを使用してすべてのサーバーにアクセスできますが、約30台のマシンを見ているため、可能であれば手動の手順を避けたいと思います。これは、SQL Server 2008以降でのみ機能する必要があり、他のSQL Serverサービス(SSIS / SSAS / SSRS)について知っておくといいのですが、主な焦点はSQL Server自体にあります。


回答:


12

将来に役立つものが必要な場合は、おそらくレジストリを検索しようとすることを避けます。SQL Serverのハイブは長年にわたって少し変わっており、それに追いつくのは面倒です。

メソッドSqlDataSourceEnumeratorは時々不安定であり、私はそれを使用しますが、インスタンスがネットワーク上にあるという具体的な証拠ではありません。SQL Browserサービスにも依存していると思いますが、ほとんどの場合は無効になっています。

WMIクラスを利用しwin32_Serviceます。これは、Get-Serviceコマンドレットよりも多くのサービスに関する情報を提供するために使用します。

これを使用して、トラブルシューティングのためにサービスの毎日のチェックまたは検証を実際に行うことができるため、一般的にすべてを関数として記述します。

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 myserverDOSプロンプトとに-Quietフラグは単純にそれを持っては返しますtruefalse。これはデフォルトで4つのpingであるため、設定を-Count 2行うと、代わりに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-ADComputerコマンドレットでドメイン上のすべてのサーバーのリストを取得できます。ただし-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情報を返す実際のサーバー名を含めるためにプロパティを出力に追加しましたが、他の関数でこれを行うと、一般的に一度に複数のサーバーでこれを使用しないので気が狂いました。


最初からサーバーのリストが提供されていれば機能します。常にそうとは限りません。
トーマスストリンガー

明確にするために、スキャンをポート1433に制限すると、名前付きインスタンスのみ(または別のポートを使用するようにハードコードされたデフォルトインスタンス)のサーバーは除外されます。大したことではないかもしれませんが、そのポートを企業全体で閉鎖した偏執的な人々がた​​くさんいます。
アーロンバートランド

確かに、それは出発点にすぎません。通常、ポートが設定されているクライアントには、通常、それらのサーバーが認識されています(それらを認識しています)。ブライアンケリーがこの方法を見つけましたが、試していません。

レジストリメソッドと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)を調べることです。Windows認証を使用してSQL Serverにリモートで接続すると、認証プロセスでSPNが使用されます。SPNの存在は、サーバー/インスタンスが確実に存在して実行されていることを意味するものではありませんが、他のアプローチのいくつかについてより包括的なものであるとわかった可能なインスタンスのリストを提供します。

生活を楽にするために、次のGet-SPNコマンドレットを使用します。 ます 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を取得できないことに気付きました(またはメンテナンスログにそのような警告があります)。彼らはまだそのスクリプトを使用して表示されますか?
エルジマー

これは通常、サービスがドメイン管理者またはローカルシステム(SPNの作成に必要)ではないユーザーとして実行されるためです。ドメイン管理者は、おそらくSetSPNユーティリティとそのドメイン管理者アカウントを使用してSPNを追加しているため、ドメイン認証はこれらのマシンで適切に機能します。そうそう、はい。
マット

0

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

名前付きインスタンスとデフォルトインスタンスを取得する必要があります。マシンのリストなどを繰り返します。


-4

これを試してみました:[System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources()

多くのデータは返されませんでしたが、VM環境で実行しているすべてのSQLサーバーを検出しました。


6
このメソッドは、トーマスストリンガーのanswerにすでに含まれています
MDCCL
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.