You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化从SQL获取服务器列表并执行Ping测试的PowerShell脚本

Hey there, let's dig into why your script's running slow and fix it up with practical optimizations. I've spotted several key bottlenecks (plus a critical bug!) that are dragging down performance:

Key Optimizations & Fixes

1. Replace Sequential Pings with Parallel Checks

Your current script pings servers one at a time, which wastes tons of waiting time. Switching to parallel checks will cut your runtime drastically. For PowerShell 7+, use ForEach-Object -Parallel (simple and efficient); for Windows PowerShell, you could use runspaces, but parallel is cleaner.

# PowerShell 7+ parallel ping example
$pingStatuses = $serverList | ForEach-Object -Parallel {
    $computerName = $_
    Write-Verbose "Checking connectivity to $computerName"
    $isPingable = 0
    
    try {
        # Use -Quiet for fast boolean results, reduce ping count to 1 (adjust if needed)
        if (Test-Connection -ComputerName $computerName -Count 1 -Quiet -ErrorAction Stop) {
            $isPingable = 1
        }
    }
    catch {
        Write-Verbose "Ping failed for $computerName : $_"
    }

    [PSCustomObject]@{
        MachineName = $computerName
        Is_Pingable = $isPingable
    }
} -ThrottleLimit 50 # Adjust based on your network capacity (don't go too high!)

Note: Your original script had redundant Test-Connection calls in the catch block—there's no need to re-run the ping if it already failed.

2. Fix the Critical Server List Bug

Your line $Server = $ping.Item(0) doesn't get your list of machine names—it just grabs the first column object from your DataTable. This means you're probably only pinging one server (or looping through column properties by accident!). Fix it by grabbing the actual machinename values:

# Correct way to get server names from your DataTable
$serverList = $ping.Rows | ForEach-Object { $_.machinename }
# Or using SqlServer module (simpler):
$serverList = Invoke-SqlCmd -ServerInstance $MasterServerConnString -Database DB -Query "SELECT DISTINCT machinename FROM [DB].[dbo].[TABLE]" | Select-Object -ExpandProperty machinename

3. Batch SQL Updates (Stop Reopening Connections!)

Your original script opens/closes a SQL connection for every single update—this creates massive overhead. Instead, use a single connection and batch your updates, or better yet, use a Table-Valued Parameter (TVP) for the most efficient and secure approach.

Option 1: Simple Batch Update

# Build a single batch of update statements
$updateBatch = @()
foreach ($status in $pingStatuses) {
    # Escape single quotes to avoid SQL injection
    $safeName = $status.MachineName.Replace("'", "''")
    $updateBatch += "UPDATE [DB].[dbo].[TABLE] SET IS_PINGABLE = $($status.Is_Pingable) WHERE MachineName = N'$safeName';"
}

# Execute all updates in one connection
$connectionString = "Data Source=$MasterServerConnString;Integrated Security=SSPI;Initial Catalog=DB"
using ($conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)) {
    $conn.Open()
    $cmd = $conn.CreateCommand()
    $cmd.CommandText = $updateBatch -join "`n"
    $rowsAffected = $cmd.ExecuteNonQuery() # Use ExecuteNonQuery for updates, not ExecuteReader
    Write-Verbose "Updated $rowsAffected rows"
}

Option 2: Table-Valued Parameter (Most Efficient & Secure)

First, create a custom table type in your SQL database:

CREATE TYPE dbo.PingStatusType AS TABLE (
    MachineName NVARCHAR(128) PRIMARY KEY,
    Is_Pingable BIT
)

Then use it in PowerShell:

# Convert ping results to a DataTable matching the SQL type
$tvpTable = New-Object System.Data.DataTable
$tvpTable.Columns.Add("MachineName", [string]) | Out-Null
$tvpTable.Columns.Add("Is_Pingable", [bool]) | Out-Null

foreach ($status in $pingStatuses) {
    $row = $tvpTable.NewRow()
    $row["MachineName"] = $status.MachineName
    $row["Is_Pingable"] = [bool]$status.Is_Pingable
    $tvpTable.Rows.Add($row)
}

# Execute the batch update
$connectionString = "Data Source=$MasterServerConnString;Integrated Security=SSPI;Initial Catalog=DB"
using ($conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)) {
    $conn.Open()
    $cmd = $conn.CreateCommand()
    $cmd.CommandText = @"
UPDATE t
SET t.IS_PINGABLE = tvp.Is_Pingable
FROM [DB].[dbo].[TABLE] t
INNER JOIN @PingStatus tvp ON t.MachineName = tvp.MachineName
"@
    $tvpParam = $cmd.Parameters.Add("@PingStatus", [System.Data.SqlDbType]::Structured)
    $tvpParam.Value = $tvpTable
    $tvpParam.TypeName = "dbo.PingStatusType"

    $rowsAffected = $cmd.ExecuteNonQuery()
    Write-Verbose "Updated $rowsAffected rows"
}

4. Simplify SQL Data Retrieval

Your original DataTable loading code can be simplified with SqlDataAdapter (no need to manually open/close connections):

$ping_cmd = "SET NOCOUNT ON; SELECT DISTINCT machinename FROM [DB].[dbo].[TABLE] ORDER BY machinename"
$ping_cn = New-Object System.Data.SqlClient.SqlConnection("Data Source=$MasterServerConnString;Integrated Security=SSPI;Initial Catalog=DB")
$ping_da = New-Object System.Data.SqlClient.SqlDataAdapter($ping_cmd, $ping_cn)
$ping = New-Object System.Data.DataTable
$ping_da.Fill($ping)
$ping_cn.Dispose() # Clean up resources properly
Full Optimized Script (PowerShell 7+)

Here's the complete script with all fixes and optimizations:

# Configuration
$MasterServerConnString = "dbserver,1433"
$DatabaseName = "DB"
$TableName = "[DB].[dbo].[TABLE]"
$ThrottleLimit = 50

#region Get Server List
Write-Verbose "Retrieving server list from SQL..."
$serverList = Invoke-SqlCmd -ServerInstance $MasterServerConnString -Database $DatabaseName -Query "SET NOCOUNT ON; SELECT DISTINCT machinename FROM $TableName ORDER BY machinename" | Select-Object -ExpandProperty machinename
#endregion

#region Parallel Ping Checks
Write-Verbose "Starting parallel ping tests..."
$pingStatuses = $serverList | ForEach-Object -Parallel {
    $computerName = $_
    $isPingable = 0
    try {
        if (Test-Connection -ComputerName $computerName -Count 1 -Quiet -ErrorAction Stop) {
            $isPingable = 1
        }
    }
    catch {
        Write-Verbose "Ping failed for $computerName : $_"
    }
    [PSCustomObject]@{
        MachineName = $computerName
        Is_Pingable = $isPingable
    }
} -ThrottleLimit $ThrottleLimit
#endregion

#region Batch Update to SQL (TVP Method)
Write-Verbose "Updating results in SQL..."
$tvpTable = New-Object System.Data.DataTable
$tvpTable.Columns.Add("MachineName", [string]) | Out-Null
$tvpTable.Columns.Add("Is_Pingable", [bool]) | Out-Null

foreach ($status in $pingStatuses) {
    $row = $tvpTable.NewRow()
    $row["MachineName"] = $status.MachineName
    $row["Is_Pingable"] = [bool]$status.Is_Pingable
    $tvpTable.Rows.Add($row)
}

$connectionString = "Data Source=$MasterServerConnString;Integrated Security=SSPI;Initial Catalog=$DatabaseName"
using ($conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)) {
    $conn.Open()
    $cmd = $conn.CreateCommand()
    $cmd.CommandText = @"
UPDATE t
SET t.IS_PINGABLE = tvp.Is_Pingable
FROM $TableName t
INNER JOIN @PingStatus tvp ON t.MachineName = tvp.MachineName
"@
    $tvpParam = $cmd.Parameters.Add("@PingStatus", [System.Data.SqlDbType]::Structured)
    $tvpParam.Value = $tvpTable
    $tvpParam.TypeName = "dbo.PingStatusType"

    $rowsAffected = $cmd.ExecuteNonQuery()
    Write-Verbose "Successfully updated $rowsAffected rows"
}
#endregion

内容的提问来源于stack exchange,提问作者Jeremy F.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:50:33