如何使我的Powershell Get-WMIObject脚本更快地通过服务器循环

时间:2013-04-24 14:04:51

标签: sql sql-server performance sql-server-2008 powershell

我正在编写一个脚本,用于检查服务器的ping,然后检查所有SQL Server服务状态并将其存储在表中。它还适用于SQL实例。但是我必须检查70台服务器,运行时间超过一分钟。我查看了AsJob参数,当我将其添加到我的所有“Get_WMIObject xxService”命令时,它开始为每个服务返回错误的信息。即它开始为所有人返回“运行”状态,所以我怀疑它只是重复以前的数据捕获。虽然它确实在20秒内运行。有人可以看看下面的内容,并告诉我哪里出错或者我可以做些什么来使异步检索服务器服务信息?

我认为将哈希表中的所有服务器的服务状态存储起来并一次更新SQL DataTable可能会更好,但证明它非常复杂。我觉得我的代码需要完全重新思考才能异步运行,但我试图避免这种情况!

提前致谢

<#
Tests all Servers for Ping and SQL Server Service Status
#>

# Return the Ping Status
Function GetStatusCode 
{  
    Param([int] $StatusCode)   
    switch($StatusCode) 
    { 
        0         {"Success"} 
        11001   {"Buffer Too Small"} 
        11002   {"Destination Net Unreachable"} 
        11003   {"Destination Host Unreachable"} 
        11004   {"Destination Protocol Unreachable"} 
        11005   {"Destination Port Unreachable"} 
        11006   {"No Resources"} 
        11007   {"Bad Option"} 
        11008   {"Hardware Error"} 
        11009   {"Packet Too Big"} 
        11010   {"Request Timed Out"} 
        11011   {"Bad Request"} 
        11012   {"Bad Route"} 
        11013   {"TimeToLive Expired Transit"} 
        11014   {"TimeToLive Expired Reassembly"} 
        11015   {"Parameter Problem"} 
        11016   {"Source Quench"} 
        11017   {"Option Too Big"} 
        11018   {"Bad Destination"} 
        11032   {"Negotiating IPSEC"} 
        11050   {"General Failure"} 
        default {"Failed"} 
    } 
} 

# Format the Server Up-time
Function GetUpTime 
{ 
    param([string] $LastBootTime) 
    $Uptime = (Get-Date) - [System.Management.ManagementDateTimeconverter]::ToDateTime($LastBootTime) 
    "Days: $($Uptime.Days); Hours: $($Uptime.Hours); Minutes: $($Uptime.Minutes); Seconds: $($Uptime.Seconds)"  
} 

#Main Body
# Populate Table MyDB.dbo.tbl_ServerPingTest

$conn = New-Object System.Data.SqlClient.SqlConnection("Data Source=MyServer; Initial Catalog=MyDB; Integrated Security=SSPI")
$conn.Open()

$cmd = $conn.CreateCommand()
$cmd.CommandText ="DELETE FROM MyDB.dbo.tbl_ServerPingTest
INSERT INTO MyDB.dbo.tbl_ServerPingTest (ServerName, InstanceName)
SELECT ServerName, InstanceName FROM MyDB.dbo.tbl_servers
WHERE ServerCategory <> 'DECOMMED'"
$cmd.ExecuteNonQuery()

$cmd2 = $conn.CreateCommand()
$cmd2.CommandText = "SELECT * FROM MyDB.dbo.tbl_ServerPingTest"
$da = New-Object System.Data.SqlClient.SqlDataAdapter #($cmd2)
$da.SelectCommand = $cmd2
$dt = New-Object System.Data.DataTable
$da.Fill($dt) | Out-Null

# Cycle through Server and Instances and retrieve information
Foreach($row in $dt.rows) 
{ 
    $ServerName = $row.ServerName
    $InstanceName = $row.InstanceName

    $pingStatus = Get-WmiObject -Query "Select * from win32_PingStatus where Address='$ServerName'" 

    $Uptime = $null 
    $SQLServerStatus = $null 
    $SQLAgentStatus = $null 

    # Enter the Loop if a Server is Pingable
    if($pingStatus.StatusCode -eq 0) 
    { 
        # Trap needed for server where Access is Denied causes the SQL Job to fail
        trap {continue}
        $OperatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $ServerName -ErrorAction SilentlyContinue -ErrorVariable wmiResults
        $Uptime = GetUptime( $OperatingSystem.LastBootUpTime ) 

        if ($wmiResults -ne $null)
          {
            $tmperr = "Uptime Info Could Not be Obtained"
            $Uptime = $null
          }
        else
          {
            $tmperr = ""

            filter SvcFilter {
                if ($_.StartMode -eq "Disabled") {$_.StartMode }
                else {$_.State}
                }

            $props="Name","StartMode","State"

            if ($InstanceName -eq 'DEFAULT') 
                {
                    $SQLServerStatus = Get-WMIObject win32_service -property $props -filter "name='MSSQLSERVER'" -computer $ServerName | SvcFilter
                    $SQLAgentStatus = Get-WMIObject win32_service -property $props -filter "name='SQLSERVERAGENT'" -computer $ServerName | SvcFilter
                    $RSAgentStatus = Get-WMIObject win32_service -property $props -filter "name='ReportServer'" -computer $ServerName | SvcFilter
                }
                else
                {
                    $NamedInstanceSQLService = "MSSQL$" + $InstanceName
                    $NamedInstanceAgentService = "SQLAgent$" + $InstanceName
                    $NamedInstanceRSService = "ReportServer$" + $InstanceName
                    $SQLServerStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceSQLService} | SvcFilter
                    $SQLAgentStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceAgentService} | SvcFilter
                    $RSAgentStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceRSService} | SvcFilter
                }

            $ASAgentStatus = Get-WMIObject win32_service -property $props -filter "name='MSSQLServerOLAPService'" -computer $ServerName | SvcFilter

          }
    } 

    $IPAddress = $pingStatus.IPV4Address
    $PingTest = GetStatusCode( $pingStatus.StatusCode ) 
    $ErrMSG = $tmperr

    # Update Table MyDB.dbo.tbl_ServerPingTest with all retreived information 
    $updateRow = $dt.Select("ServerName = '$ServerName' AND InstanceName = '$InstanceName'")
    $updateRow[0].IPAddress = $IPAddress
    $updateRow[0].PingTest = $PingTest
    $updateRow[0].ErrMSG = $ErrMSG
    $updateRow[0].Uptime = $Uptime
    $updateRow[0].SQLServerStatus = $SQLServerStatus
    $updateRow[0].SQLAgentStatus = $SQLAgentStatus   
    $updateRow[0].RSAgentStatus = $RSAgentStatus 
    $updateRow[0].ASAgentStatus = $ASAgentStatus 

    $cmdUpd = $conn.CreateCommand()
    $cmdUpd.CommandText = "UPDATE MyDB.dbo.tbl_ServerPingTest
    SET IPAddress = @IPAddress, PingTest = @PingTest, ErrMSG = @ErrMSG, Uptime = @Uptime, SQLServerStatus = @SQLServerStatus, SQLAgentStatus = @SQLAgentStatus, RSAgentStatus = @RSAgentStatus, ASAgentStatus = @ASAgentStatus
    WHERE ServerName = @ServerName AND InstanceName = @InstanceName"

    # Add parameters to pass values to the UPDATE statement
    $cmdUpd.Parameters.Add("@ServerName", "nvarchar", 50, "ServerName") | Out-Null
    $cmdUpd.Parameters["@ServerName"].SourceVersion = "Original"
    $cmdUpd.Parameters.Add("@InstanceName", "nvarchar", 50, "InstanceName") | Out-Null
    $cmdUpd.Parameters["@InstanceName"].SourceVersion = "Original"
    $cmdUpd.Parameters.Add("@IPAddress", "nvarchar", 50, "IPAddress") | Out-Null
    $cmdUpd.Parameters["@IPAddress"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@PingTest", "nvarchar", 50, "PingTest") | Out-Null
    $cmdUpd.Parameters["@PingTest"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@ErrMSG", "nvarchar", 50, "ErrMSG") | Out-Null
    $cmdUpd.Parameters["@ErrMSG"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@Uptime", "nvarchar", 50, "Uptime") | Out-Null
    $cmdUpd.Parameters["@Uptime"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@SQLServerStatus", "nvarchar", 50, "SQLServerStatus") | Out-Null
    $cmdUpd.Parameters["@SQLServerStatus"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@SQLAgentStatus", "nvarchar", 50, "SQLAgentStatus") | Out-Null
    $cmdUpd.Parameters["@SQLAgentStatus"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@RSAgentStatus", "nvarchar", 50, "RSAgentStatus") | Out-Null
    $cmdUpd.Parameters["@RSAgentStatus"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@ASAgentStatus", "nvarchar", 50, "ASAgentStatus") | Out-Null
    $cmdUpd.Parameters["@ASAgentStatus"].SourceVersion = "Current"

    # Set the UpdateCommand property
    $da.UpdateCommand = $cmdUpd

    # Update the database
    $RowsUpdated = $da.Update($dt)


} 

$conn.Close()

我尝试将-AsJob参数添加到每个Get-WMIObject行

$SQLServerStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceSQLService} -AsJob | SvcFilter

但我没有在代码中的任何地方使用Receive-Job。我注意到,即使它是“已停止”或“已禁用”,每个变量都会返回“正在运行”状态。所以我猜测Job的结果没有被正确捕获,它实际上是返回先前Get-WMIObject调用的输出(其中大多数将是“Running”状态)。

3 个答案:

答案 0 :(得分:0)

get-wmiobject有一个-asjob参数,但where-object没有。

此外,当您使用-asjob时,返回值是“作业对象”,而不是例如get-wmiobject调用返回的对象。

在远程服务器上完成后,您可以使用receive-job cmdlet从每个作业中获取实际对象(并且您可以使用wait-jobget-job来帮助了解何时它完成了。

我建议您使用get-help about_Remote_Jobs来了解如何构建代码的背景信息。

答案 1 :(得分:0)

如果你的get-wmiObject wantsshorter timeoutget-wmiCustom function可能有所帮助。

答案 2 :(得分:0)

我设法使用Start-Job重写,并将其从80秒减少到20秒。如果有人对如何使其更有效率有任何进一步的建议,我将不胜感激。我正在考虑一次性检索所有WMIObjects而不是逐个检索?任何人都可以提出建议吗?

#Main Body
# Populate Table MyDB.dbo.tbl_ServerPingTest

$conn = New-Object System.Data.SqlClient.SqlConnection("Data Source=MyServer; Initial Catalog=MyDB; Integrated Security=SSPI")
$conn.Open()

$cmd = $conn.CreateCommand()
$cmd.CommandText ="DELETE FROM MyDB.dbo.tbl_ServerPingTest
INSERT INTO MyDB.dbo.tbl_ServerPingTest (ServerName, InstanceName)
SELECT ServerName, InstanceName FROM MyDB.dbo.tbl_servers
WHERE ServerCategory <> 'DECOMMED'"
$cmd.ExecuteNonQuery()

$cmd2 = $conn.CreateCommand()
$cmd2.CommandText = "SELECT * FROM MyDB.dbo.tbl_ServerPingTest"
$da = New-Object System.Data.SqlClient.SqlDataAdapter #($cmd2)
$da.SelectCommand = $cmd2
$dt = New-Object System.Data.DataTable
$da.Fill($dt) | Out-Null

# Create list
$serverlist = @()

$sb = {
    param ([string] $ServerName, [string] $InstanceName)

    # Return the Ping Status
    Function GetStatusCode 
    {  
        Param([int] $StatusCode)   
        switch($StatusCode) 
        { 
            0         {"Success"} 
            11001   {"Buffer Too Small"} 
            11002   {"Destination Net Unreachable"} 
            11003   {"Destination Host Unreachable"} 
            11004   {"Destination Protocol Unreachable"} 
            11005   {"Destination Port Unreachable"} 
            11006   {"No Resources"} 
            11007   {"Bad Option"} 
            11008   {"Hardware Error"} 
            11009   {"Packet Too Big"} 
            11010   {"Request Timed Out"} 
            11011   {"Bad Request"} 
            11012   {"Bad Route"} 
            11013   {"TimeToLive Expired Transit"} 
            11014   {"TimeToLive Expired Reassembly"} 
            11015   {"Parameter Problem"} 
            11016   {"Source Quench"} 
            11017   {"Option Too Big"} 
            11018   {"Bad Destination"} 
            11032   {"Negotiating IPSEC"} 
            11050   {"General Failure"} 
            default {"Failed"} 
        } 
    }

    # Format the Server Up-time
    Function GetUpTime 
    { 
        param([string] $LastBootTime) 
        $Uptime = (Get-Date) - [System.Management.ManagementDateTimeconverter]::ToDateTime($LastBootTime) 
        "Days: $($Uptime.Days); Hours: $($Uptime.Hours); Minutes: $($Uptime.Minutes); Seconds: $($Uptime.Seconds)"  
    } 

    try {

        # Fetch IP
        $pingStatus = Get-WmiObject -Query "Select * from win32_PingStatus where Address='$ServerName'"

        $Uptime = $null 
        $SQLServerStatus = $null 
        $SQLAgentStatus = $null 

        # Enter the Loop if a Server is Pingable
        if($pingStatus.StatusCode -eq 0) 
        { 
            # Trap needed for server where Access is Denied causes the SQL Job to fail
            trap {continue}
            $OperatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $ServerName -ErrorAction SilentlyContinue -ErrorVariable wmiResults
            $Uptime = GetUptime( $OperatingSystem.LastBootUpTime ) 

            if ($wmiResults -ne $null)
              {
                $tmperr = "Uptime Info Could Not be Obtained"
                $Uptime = $null
              }
            else
              {
                $tmperr = ""

                filter SvcFilter {
                    if ($_.StartMode -eq "Disabled") {$_.StartMode }
                    else {$_.State}
                    }

                $props="Name","StartMode","State"

                if ($InstanceName -eq 'DEFAULT') 
                    {
                        #Write-Host $ServerName + '\' + $InstanceName 
                        $SQLServerStatus = Get-WMIObject win32_service -property $props -filter "name='MSSQLSERVER'" -computer $ServerName | SvcFilter
                        #Write-Host $SQLServerStatus
                        $SQLAgentStatus = Get-WMIObject win32_service -property $props -filter "name='SQLSERVERAGENT'" -computer $ServerName | SvcFilter
                        #Write-Host $SQLAgentStatus
                        $RSAgentStatus = Get-WMIObject win32_service -property $props -filter "name='ReportServer'" -computer $ServerName | SvcFilter
                        #Write-Host $RSAgentStatus
                    }
                    else
                    {
                        #Write-Host $ServerName + '\' + $InstanceName 
                        $NamedInstanceSQLService = "MSSQL$" + $InstanceName
                        $NamedInstanceAgentService = "SQLAgent$" + $InstanceName
                        $NamedInstanceRSService = "ReportServer$" + $InstanceName
                        $SQLServerStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceSQLService} | SvcFilter
                        $SQLAgentStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceAgentService} | SvcFilter
                        $RSAgentStatus = Get-WMIObject win32_service -property $props -computer $ServerName | where {$_.name -eq $NamedInstanceRSService} | SvcFilter
                    }

                $ASAgentStatus = Get-WMIObject win32_service -property $props -filter "name='MSSQLServerOLAPService'" -computer $ServerName | SvcFilter

              }
        } 

        $IPAddress = $pingStatus.IPV4Address
        $PingTest = GetStatusCode( $pingStatus.StatusCode ) 
        $ErrMSG = $tmperr

        # Save info about server
        $serverInfo = New-Object -TypeName PSObject -Property @{
        ServerName = $ServerName
        InstanceName = $InstanceName
        IPAddress = $IPAddress
        PingTest = $PingTest
        ErrMSG = $ErrMSG
        Uptime = $Uptime
        SQLServerStatus = $SQLServerStatus
        SQLAgentStatus = $SQLAgentStatus   
        RSAgentStatus = $RSAgentStatus 
        ASAgentStatus = $ASAgentStatus 

        }
        return $serverInfo
    } catch {
        throw 'Failed to process server named {0}. The error was "{1}".' -f $ServerName, $_
    }
}

# Loop servers
$max = 5
$jobs = @()
foreach($row in $dt.rows) {
    #$InstanceName = "DEFAULT"
    $server = $row.ServerName
    $InstanceName = $row.InstanceName
    $jobs += Start-Job -ScriptBlock $sb -ArgumentList @($server, $InstanceName) 
    $running = @($jobs | ? {$_.State -eq 'Running'})

    # Throttle jobs.
    while ($running.Count -ge $max) {
        $finished = Wait-Job -Job $jobs -Any
        $running = @($jobs | ? {$_.State -eq 'Running'})
    }
}

# Wait for remaining.
Wait-Job -Job $jobs > $null

# Check for failed jobs.
$failed = @($jobs | ? {$_.State -eq 'Failed'})
if ($failed.Count -gt 0) {
    $failed | % {
        $_.ChildJobs[0].JobStateInfo.Reason.Message
    }
}

# Collect job data.
$jobs | % {
    $serverlist += $_ | Receive-Job | Select-Object ServerName,InstanceName,IPAddress,PingTest,ErrMSG,Uptime,SQLServerStatus,SQLAgentStatus,RSAgentStatus,ASAgentStatus
}

#$serverlist

Foreach($row in $serverlist) 
{ 
    $ServerName = $row.ServerName
    $InstanceName = $row.InstanceName

    #$dt.Select("ServerName = '$ServerName' AND InstanceName = '$InstanceName'")

    $IPAddress = $row.IPAddress
    $PingTest = $row.PingTest
    $ErrMSG = $row.ErrMSG
    $Uptime = $row.Uptime
    $SQLServerStatus = $row.SQLServerStatus
    $SQLAgentStatus = $row.SQLAgentStatus   
    $RSAgentStatus = $row.RSAgentStatus 
    $ASAgentStatus = $row.ASAgentStatus 


   # Write-Host $ServerName
   # Write-Host $InstanceName

    $updateRow = $dt.Select("ServerName = '$ServerName' AND InstanceName = '$InstanceName'")
    $updateRow[0].IPAddress = $IPAddress
    $updateRow[0].PingTest = $PingTest
    $updateRow[0].ErrMSG = $ErrMSG
    $updateRow[0].Uptime = $Uptime
    $updateRow[0].SQLServerStatus = $SQLServerStatus
    $updateRow[0].SQLAgentStatus = $SQLAgentStatus   
    $updateRow[0].RSAgentStatus = $RSAgentStatus 
    $updateRow[0].ASAgentStatus = $ASAgentStatus 

    $cmdUpd = $conn.CreateCommand()
    $cmdUpd.CommandText = "UPDATE MyDB.dbo.tbl_ServerPingTest
    SET IPAddress = @IPAddress, PingTest = @PingTest, ErrMSG = @ErrMSG, Uptime = @Uptime, SQLServerStatus = @SQLServerStatus, SQLAgentStatus = @SQLAgentStatus, RSAgentStatus = @RSAgentStatus, ASAgentStatus = @ASAgentStatus
    WHERE ServerName = @ServerName AND InstanceName = @InstanceName"

    # Add parameters to pass values to the UPDATE statement
    $cmdUpd.Parameters.Add("@ServerName", "nvarchar", 50, "ServerName") | Out-Null
    $cmdUpd.Parameters["@ServerName"].SourceVersion = "Original"
    $cmdUpd.Parameters.Add("@InstanceName", "nvarchar", 50, "InstanceName") | Out-Null
    $cmdUpd.Parameters["@InstanceName"].SourceVersion = "Original"
    $cmdUpd.Parameters.Add("@IPAddress", "nvarchar", 50, "IPAddress") | Out-Null
    $cmdUpd.Parameters["@IPAddress"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@PingTest", "nvarchar", 50, "PingTest") | Out-Null
    $cmdUpd.Parameters["@PingTest"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@ErrMSG", "nvarchar", 50, "ErrMSG") | Out-Null
    $cmdUpd.Parameters["@ErrMSG"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@Uptime", "nvarchar", 50, "Uptime") | Out-Null
    $cmdUpd.Parameters["@Uptime"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@SQLServerStatus", "nvarchar", 50, "SQLServerStatus") | Out-Null
    $cmdUpd.Parameters["@SQLServerStatus"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@SQLAgentStatus", "nvarchar", 50, "SQLAgentStatus") | Out-Null
    $cmdUpd.Parameters["@SQLAgentStatus"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@RSAgentStatus", "nvarchar", 50, "RSAgentStatus") | Out-Null
    $cmdUpd.Parameters["@RSAgentStatus"].SourceVersion = "Current"
    $cmdUpd.Parameters.Add("@ASAgentStatus", "nvarchar", 50, "ASAgentStatus") | Out-Null
    $cmdUpd.Parameters["@ASAgentStatus"].SourceVersion = "Current"

    # Set the UpdateCommand property
    $da.UpdateCommand = $cmdUpd

    # Update the database
    $RowsUpdated = $da.Update($dt)

}

$conn.Close()