PowerShell kullanarak SQL Server'ın çalışan tüm örneklerini keşfetmenin en etkili yolu nedir?


13

Etki alanımızda çalışan tüm SQL Server örneklerini keşfetmekle görevlendirildim. Bazı durumlarda, sunucu başına birden çok örnek vardır. Bu örnekleri bulmak için iki farklı PowerShell yöntemi gördüm, ancak hiçbiri tüm örnekleri bulamıyor.

1) WMI kullanın

        $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) Uzak kayıt defteri kullanın (Get-SQLInstance 1 ile olduğu gibi )

İçinde çalıştığım en büyük sorun, bildiğim tüm sunucuların SQL Server WMI sağlayıcısıyla çalıştırılmadığı veya hepsinin uzak kayıt defterine izin vermemesidir. Üçüncü bir yöntem var mı? Tüm sunuculara erişmek için Uzak Masaüstü'nü kullanabilirim ancak yaklaşık 30 makineye bakıyorum ve mümkünse manuel adımlardan kaçınmak istiyorum. Bunun yalnızca SQL Server 2008 ve üstü için çalışması gerekir ve diğer SQL Server hizmetleri (SSIS / SSAS / SSRS) hakkında bilmek güzel olsa da, ana odak noktam SQL Server'ın kendisidir.


Yanıtlar:


12

Gelecek için yararlı olacak bir şey istiyorsanız, büyük olasılıkla kayıt defterini aramaya çalışırken yönlendirirsiniz. SQL Server için kurdeşen yıllar içinde biraz değişti ve yetişmek zahmetli olabilir.

SqlDataSourceEnumeratorİle yöntem zaman zaman lapa lapa ve ben kullanacak olsam da, örneklerin ağ üzerinde olduğuna dair somut bir kanıt değil. Ben de çoğu zaman devre dışı buluyorum SQL Tarayıcı Hizmeti, bağlı olduğuna inanıyorum.

WMI sınıfını kullanacağım win32_Service. Bunu kullanıyorum çünkü Get-Servicecmdlet'ten daha fazla hizmet sunuyor .

Her şeyi genellikle fonksiyon olarak yazıyorum, çünkü bunu sadece sorun giderme için günlük kontrol veya doğrulama yapmak için kullanabilirsiniz.

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

Bu genellikle kullandığımdan biraz daha fazladır, ancak başka birinin karşılaşması ve kullanmak istemesi durumunda. Test-Connectionİçin eşittir ping myserverDOS istemi ve -Quietbayrak sadece sadece o vardır dönmek trueveya false. Bu varsayılan olarak 4 ping olacaktır, böylece ayar -Count 2sadece iki kez yapar.

Değişken [string[]]$server, bir $serversunucu adı dizisini kabul edecek bir yöntemdir . Yani bu işlevin örnek çağrısı şöyle görünebilir:

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

veya

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

DÜZENLE

Belirtilen bir yorum, yukarıda sunulan sunucuların listesine bağlıdır. Bu listenin sağlanmadığı durumlarda birkaç seçeneğiniz daha vardır.

  • Active Directory ortamındaysanız , cmdlet ile etki alanındaki tüm sunucuların listesini almak için PowerShell'deki ActiveDirectory modülünü kullanabilirim Get-ADComputer. Bir uyarı sözcüğü olsa da -Filter, büyük alanlarda iyi kullandığınızdan emin olun .

  • Ayrıca sadece bana 1433 numaralı bağlantı noktasının açık olduğu IP adreslerini veren bir ağın IP taramasını (onaylı) yaptım. Bu IP listesini alıp Get-ADComputeretki alanı bilgisayar adlarını bulmak için kullanacağım , sonra yukarıdaki işleve geçireceğim

Misal:

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

DÜZENLE

Kullanmak Write-Verboseve denemek / yakalamak blok eklemek için önerilen düzenleme , bu yararlı olabilir ve çoğu durumda bir kod uygulama, ben bu ek kod veya işlevsellik eklemek için bu işlevi kullanmak isteyen kişiye bırakacaktır. Sadece devam etmek için temel bir örnek sunmaya çalışıyoruz. Ben SystemNamegerçek sunucu adı dönen bilgileri eklemek için çıkış özelliği ekledim, diğer işlevler üzerinde bunu yapmak sadece genel olarak bu bir kerede birden fazla sunucu için kullanmayın bu yüzden aklımı kaydırdı.


Başlamak için bir sunucu listesi sağlamanız şartıyla çalışır. Bu her zaman varsayılamaz.
Thomas Stringer

Sadece taramayı 1433 numaralı bağlantı noktasıyla sınırlandırmak için, yalnızca adlandırılmış örneklere (veya farklı bir bağlantı noktası kullanmak için sabit kodlanmış varsayılan örneklere sahip) sahip tüm sunucuları kaldırabilirsiniz. Belki de büyük bir anlaşma değil ama o liman boyunca kurumsal çapta kapattı bol paranoyak millet vardır.
Aaron Bertrand

Doğru, bu sadece bir başlangıç ​​noktası. Bağlantı noktalarının genellikle belirlendiği noktalar, istemcilerin genellikle bu sunuculara sahip olduklarını fark ettim (bunların farkında). Bu yöntemi Brian Kelley tarafından buldum ama denemedim.

Ben hem kayıt defteri yöntemini hem de win32_service WMI'yi bir yedek olarak birleştirmenin sunucuların çoğunu alması gerektiğini düşünüyorum ve daha sonra geri kalanın manuel olarak araması işe yarayacaktır. Hoş bir yan etki olarak, çalışan ancak gerekli olmayan, erişimime izin vermeyen sunucular vb. Hizmetler hakkında da bazı bilgiler alabilirim
Elsimer

5

Tüm olası sahip sunucuları ve özel adlarını bilmeden bir ortamdaki örnekleri keşfetmenin tek yolu, System.Data.Sql.SqlDataSourceEnumerator.GetDataSources () işlevine çağrı yapmaktır . Bununla birlikte, bu yöntem çok sayıda dipnot ile birlikte gelir. Doğrudan bu MSDN kaynağından alınan bir snippet:

SqlDataSourceEnumerator tarafından bir ağdaki veri kaynaklarını bulmak için kullanılan mekanizmanın doğası nedeniyle , yöntem her zaman kullanılabilir sunucuların tam bir listesini döndürmez ve liste her çağrıda aynı olmayabilir. Kullanıcıların listeden bir sunucu seçmesine izin vermek için bu işlevi kullanmayı planlıyorsanız, sunucu numaralandırmasının kullanılabilir tüm sunucuları döndürmemesi durumunda, listede olmayan bir ad yazma seçeneği de sağladığınızdan emin olun. . Ek olarak, bu yöntemin yürütülmesi önemli miktarda zaman alabilir , bu nedenle performans kritik olduğunda çağırmaya dikkat edin.

PowerShell'den çağrı basittir:

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

Bu yöntem DataTable, buna göre işleyebileceğiniz bir nesne döndürür .


3

SQL Tarayıcı Hizmeti etkinse, aşağıdaki PowerShell kodu ile SQL Örnekleri için hizmeti sorgulayabilirsiniz. Sorguları gerçekleştirmek için aşağıdaki komutları uygular:

  • 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

Olası SQL Örneklerini tanımlamanın başka bir yolu, Active Directory'de listelenen Hizmet İlkesi Adlarına (SPN) bakmaktır. SQL Server'a Windows Kimlik Doğrulaması ile uzaktan bağlandığınızda, kimlik doğrulama işleminde bir SPN kullanılır. Bir SPN'nin varlığı, sunucunun / yönetim ortamının kesinlikle orada olduğu ve çalıştığı anlamına gelmez, ancak diğer yaklaşımlardan bazılarını daha kapsamlı bulduğum olası örneklerin bir listesini verir.

Hayatı kolaylaştırmak için Get-SPN cmdlet'ini şu adresten kullanıyorum: https://gallery.technet.microsoft.com/scriptcenter/Get-SPN-Get-Service-3bd5524a

Get-SPN.ps1 komut dosyasını indirin, C: \ powershell_scripts \ Get-SPN.ps1 klasörüne kaydedin ve PowerShell'de aşağıdakileri çalıştırın:

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

(Açıkçası komut dosyasını başka bir konuma kaydedebilirsiniz, sadece ilk satırı gerektiği gibi güncelleyin.)

Bu, hizmetin bağlantı noktası / örneği ile ilgili "belirtim" de dahil olmak üzere, geçerli etki alanındaki tüm SQL Server SPN'lerini listeler.


SQL Server makinelerimizin çoğunun SPN'leri (veya bakım günlüğünde böyle bir uyarıyı) alamadığını fark ettim. Hala bu senaryoyu kullanarak görünecekler mi?
Elsimer

Bunun nedeni genellikle hizmetin etki alanı yöneticisi veya yerel sistem olmayan bir kullanıcı olarak çalışmasıdır (SPN oluşturmak için gereklidir). Etki alanı yöneticisi muhtemelen SetSPN yardımcı programını ve etki alanı yönetici hesabını kullanarak SPN ekledi, böylece etki alanı yetkilendirmesi bu makineler için düzgün çalışıyor. Çok muhtemel, evet.
Mat

0

Get-Service -BilgisayarAdı * MSSQL * | Where-Object {$ _. Status -eq "Çalışıyor"}

Bu adlandırılmış ve varsayılan örnekleri almalısınız. Sadece makine listenizi tekrarlayın, vb.


-4

Sadece şunu denedim: [System.Data.Sql.SqlDataSourceEnumerator] :: Instance.GetDataSources ()

Çok fazla veri döndürmedi ama bir VM ortamında çalıştırdığım tüm sql sunucularını algıladı.


6
Bu yöntem Thomas Stringer'ın cevabına zaten dahil edilmiştir .
MDCCL
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.