2010-07-13 9 views
8

Tengo una secuencia de comandos que ejecutará y hará ping a todos los servidores de una lista que está almacenada en SQL Server. El script funciona bien pero lo hace todo secuencialmente (cojo).Ayuda de subprocesamiento múltiple con Powershell

¿Alguien puede ayudarme a cómo cambiaría esto para usar multiprocesamiento en lugar de un ciclo foreach?

$Server = "ServerName" 
$Database = "DatabaseName" 

$con = "server=$Server;database=$Database;Integrated Security=sspi" 
$cmd = "SELECT ServerName FROM dbo.vwServerListActive" 

    $da = new-object System.Data.SqlClient.SqlDataAdapter ($cmd, $con) 

    $dt = new-object System.Data.DataTable 

    $da.fill($dt) | out-null 


    foreach ($srv in $dt) 
    { 

    $ping = new-object System.Net.NetworkInformation.Ping 
    $Reply = $ping.send($srv.ServerName) 

    $ServerName = $srv.ServerName 

    $ServerName 
    $Reply.status 

    if ($Reply.status –eq “Success”) 
    { 
     $sql = "UPDATE dbo.ServerList SET GoodPing = 1 WHERE GoodPing <> 1 AND ServerName = '$ServerName'" 

    } 
    else 
    { 
     $sql = "UPDATE dbo.ServerList SET GoodPing = 0 WHERE GoodPing <> 0 AND ServerName = '$ServerName'" 
    } 

    $Reply = "" 

    invoke-sqlcmd -serverinstance $Server -database $Database -query $sql 


    } 

Respuesta

2

Si tiene PowerShell 2.0, puede hacer uso de trabajos en segundo plano. Tendrá que dividir su lista de servidores en "grupos".Dada una tabla de origen con serverName y nombre_de_grupo:

CREATE TABLE [dbo].[vwServerListActive](
    [serverName] [varchar](50) NULL, 
    [groupName] [char](1) NULL 
) 

Una ligera modificación de la secuencia de comandos (guardar como forum.ps1):

param($groupName) 

$Server = "$env:computername\sql2k8" 
$Database = "dbautility" 

$con = "server=$Server;database=$Database;Integrated Security=sspi" 
$cmd = "SELECT ServerName FROM dbo.vwServerListActive WHERE groupName ='$groupName'" 

    $da = new-object System.Data.SqlClient.SqlDataAdapter ($cmd, $con) 

    $dt = new-object System.Data.DataTable 

    $da.fill($dt) | out-null 


    foreach ($srv in $dt) 
    { 

    $ping = new-object System.Net.NetworkInformation.Ping 
    $Reply = $ping.send($srv.ServerName) 

    new-object PSObject -Property @{ServerName=$($srv.ServerName); Reply=$($Reply.status)} 

    } 

A continuación, puede llamar a la secuencia de comandos para los diferentes grupos:

#groupName A 
start-job -FilePath .\forum.ps1 -Name "Test" -ArgumentList "A" 
#groupName B 
start-job -FilePath .\forum.ps1 -Name "Test" -ArgumentList "B" 

Get-Job -name "test" | wait-job | out-null 
Get-Job -name "test" | receive-job 

#get-job -name "test" |remove-job 

Si está utilizando PowerShell V1 o sqlps, puede usar System.Diagnostics.ProcessStartInfo para iniciar procesos independientes de powershell.exe y pasar el nombre del grupo.

param($groupName) 

    $StartInfo = new-object System.Diagnostics.ProcessStartInfo 
    $StartInfo.FileName = "$pshome\powershell.exe" 
    $StartInfo.Arguments = " -NoProfile -Command C:\scripts\forum.ps1 $groupName" 
    $StartInfo.WorkingDirectory = "C:\scripts" 
    $StartInfo.LoadUserProfile = $true 
    $StartInfo.UseShellExecute = $true 
    [System.Diagnostics.Process]::Start($StartInfo) > $null 
+0

, entonces lo que está sugiriendo es mantener mi procesamiento bastante bien tal como está y comenzar x número de sesiones de PowerShell (¿dividir mi lista de servidores de manera uniforme en todas las agrupaciones)? Tener un trabajo por separado para cada corrida parece que está agregando una gran cantidad de sobrecarga, así que esta podría ser una mejor solución que va a jugar y le avisaré. :) gracias – ColinStasiuk

+0

Eso es correcto al utilizar el trabajo inicial o, alternativamente, iniciar un proceso de PowerShell genera una sobrecarga para configurar el espacio de ejecución o iniciar el proceso. Debido a esto, generalmente quiere limitar el número de procesos o trabajos en segundo plano. Los tipos de procesos que haré en segundo plano pueden ejecutarse independientemente del proceso principal y tomar más de unos minutos para ejecutarse; de ​​lo contrario, no vale la pena la sobrecarga. También me gustaría sugerir que no se cargue el cmdlet SQL solo para invoke-sqlcmd, esto se suma a la sobrecarga, sino que use la llamada ADO.NET directa como mencioné en la publicación anterior. –

+0

Además, en lugar del enfoque de agrupación, he usado un par de scripts de aproximación de cola en los que configura el número de trabajos o procesos para ejecutar en paralelo, sin necesidad de agruparlos manualmente. Nota: nada de lo que estamos haciendo es un verdadero enhebrado, sino un procesamiento concurente. Descargue mi presentación de PowerShell ETL para obtener las secuencias de comandos: http://sev17.com/2010/06/powershell-etl-presentation/ –

0

Aquí hay una scripts page que podría ser útil para usted. Todavía no lo he usado, así que no puedo comentar más allá de eso.

+0

Hola Tom Gracias por el enlace ... lo gracioso es que es por lo que he estado trabajando y tratando de entender ... donde me quedo atascado es cómo: * dinámicamente llamar a Invoke-ExpressionInRunspace basado en el servidor lista que se obtiene de mi SQL DB * Pase la variable del nombre del servidor a la llamada * BONUS: Limite el número de subprocesos (es decir. 10 a la vez?) Estoy pensando que voy a tener que crear una variable $ y recorrer/agregar a ella para crear mi comando Invoke-ExpressionInRunspace No tengo idea de cómo va a volar esto LOL – ColinStasiuk

0

Powershell realmente no hace multihebra en absoluto. Me las arreglé para colocarlo en su lugar fingiéndolo con una secuencia de comandos de "fuego y olvido" iniciada con "start [powershell path] scriptname.ps1". Descarguará múltiples secretos, pero no podrá obtener datos de ellos sin realizar una ejecución final a través de una base de datos u otro mecanismo de transmisión de mensajes. El seguimiento cuando los procesos secundarios finalizan es complicado también.

cmd /c "start /min /low C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command .\evtlogparse.ps1 " 

En su caso lo que está configurando una cadena SQL como parte del bucle foreach, se puede tratar de poner el código de actualización de base de datos en una segunda secuencia de comandos que el fuego apagado. Posiblemente tendrá muchos procesos diferentes intentando actualizar la misma tabla de base de datos, por lo que la posibilidad de problemas de temporización es bastante grande.

Además, dado que estás lanzando umpty nuevas instancias de PowerShell, vas a comer mucha memoria para que funcione. El ciclo foreach puede ser más rápido que iniciar un montón de procesos.

Por lo tanto, se puede hacer, pero no es nada parecido a lo bonito.

+0

entonces qué ¿Está sugiriendo que una posible solución esté dentro de mi ciclo foreach para abrir varias instancias de powershell.exe? Sí, puedo ver dónde eso no sería bonito ... especialmente si tengo más de 1000 servidores, jejeje. No estoy seguro de cómo podría asignar el parámetro -command con la necesidad de tener el $ ServerName incluido en la llamada. – ColinStasiuk

+0

Sí, esto no escala bien. Es bueno si necesita recuperar y procesar varios archivos XML grandes al mismo tiempo. No tan bueno si solo estás haciendo ping a un servidor. Y sí, debe pasar variables a los scripts secundarios como opciones de línea de comandos. – sysadmin1138

0

Primero, sugiero crear solo una vez que la variable $ ping fuera de 'foreach ..'.
Quizás una solución más simple ... ahora que está usando SQL 2008, ¿por qué no usar el método SMO 'enumAvailableSqlServers: "... SMOApplication] :: EnumAvailableSqlServers ($ false)". Esto le dará una lista de todos los servidores disponibles en la red. Aquí está el enlace de Microsoft MSDN para que pueda leer al respecto: http://msdn.microsoft.com/en-us/library/ms210350.aspx

+0

Hola Max Buena captura en $ ping dentro del ciclo ... ¡gracias! :) No estoy usando enumAvailableSQLServers porque no solo estoy buscando hacer esto para servidores SQL. Tengo un script diferente que rellena/actualiza mi tabla/vista vwServerListActive con todos los servidores de mi dominio. gracias – ColinStasiuk

4

(Editado según la sugerencia + Throttling Requisito de Chad Miller + Wait-Job fix + fix STA)

Support.ps1

powershell -File "Main.ps1" -Sta 

Main.ps1

$Server = "ServerName" 
$Database = "DatabaseName" 

$con = "server=$Server;database=$Database;Integrated Security=sspi" 
$cmd = "SELECT ServerName FROM dbo.vwServerListActive" 

$da = New-Object System.Data.SqlClient.SqlDataAdapter -ArgumentList $cmd, $con 

$dt = New-Object System.Data.DataTable 

$da.Fill($dt) | Out-Null 

$ThrottleLimit = 10 
$activeJobs = New-Object 'System.Collections.Generic.List[Int32]' 

$JobStateChanged = { 
    param ( 
     [System.Object]$Sender, 
     [System.Management.Automation.JobStateEventArgs]$EventArgs 
    ) 

    switch ($EventArgs.JobStateInfo.State) 
    { 
     Blocked { return } 
     Completed { $activeJobs.Remove($Sender.Id); break } 
     Failed { $activeJobs.Remove($Sender.Id); break } 
     NotStarted { return } 
     Running { return } 
     Stopped { $activeJobs.Remove($Sender.Id); break } 
    } 

    Unregister-Event -SourceIdentifier ("{0}.StateChanged" -f $Sender.Name) 
} 

foreach ($srv in $dt) 
{ 
    while ($true) 
    { 
     if ($activeJobs.Count -lt $ThrottleLimit) 
     { 
      $job = Start-Job -InitializationScript { 
       Add-PSSnapin -Name SqlServerCmdletSnapin100 
      } -ScriptBlock { 
       param ( 
        [String]$Server, 
        [String]$Database, 
        [String]$ServerName 
       ) 

       if (Test-Connection -ComputerName $ServerName -Quiet) 
       { 
        $sql = "UPDATE dbo.ServerList SET GoodPing = 1 WHERE GoodPing <> 1 AND ServerName = '$ServerName'" 
       } 
       else 
       { 
        $sql = "UPDATE dbo.ServerList SET GoodPing = 0 WHERE GoodPing <> 0 AND ServerName = '$ServerName'" 
       } 

       Invoke-SqlCmd -ServerInstance $Server -Database $Database -Query $sql 
      } -ArgumentList $Server, $Database, $srv.ServerName 

      $activeJobs.Add($job.Id) 

      Register-ObjectEvent -InputObject $job -EventName StateChanged -SourceIdentifier ("{0}.StateChanged" -f $job.Name) -Action $JobStateChanged 

      break 
     } 
    } 
} 

Get-Job | Where-Object { $_.State -eq "Running" } | Wait-Job 
Get-Job | Remove-Job 
+0

Hola George Lo siento por el retraso en las respuestas pero sí conseguir que esto funcione con la adición de un espera puesto de trabajo * hasta el final de la secuencia de comandos. Cuando no tenía eso cuando el script finalizaba, mi error indicaba que "Powershell había dejado de funcionar" y mis datos no se actualizarían en la base de datos. El problema con el uso de wait-job * es que si tengo el servidor 100 ... si estoy esperando que todos los trabajos terminen con cada trabajo generando su propio proceso de Powershell, pincho mi casilla. Supongo que no hay estrangulamiento para esto? Creo que aquí es donde la sugerencia de Chad de usar grupos probablemente entra en juego. – ColinStasiuk

+0

@Colin: el guión de Chad en realidad tiene el mismo aspecto que el mío, excepto que usted lee un archivo y lo ejecuta como un ScriptBlock. He actualizado mi respuesta con el arreglo Wait-Job (lo siento) y algún código para hacer algo de regulación. Espero que ayude. –

+0

lo siento ... la última edición parece aplanarse bien ... pero después de los primeros 10 procesos de obtención/finalizados no avanza al siguiente "conjunto". Lo investigaré más a fondo, pero esta solución (cuando funcione) eliminará la necesidad de agrupar mis servidores manualmente. – ColinStasiuk

0

tan cerca .... esto es lo que tengo

add-pssnapin SqlServerCmdletSnapin100 

$Server = "ServerName" 
$Database = "DatabaseName" 

$con = "server=$Server;database=$Database;Integrated Security=sspi" 
$cmd = "SELECT ServerName FROM dbo.vwServerListActive" 

$da = New-Object System.Data.SqlClient.SqlDataAdapter -ArgumentList $cmd, $con 

$dt = New-Object System.Data.DataTable 

$da.Fill($dt) | Out-Null 


foreach ($srv in $dt) 
{  
    Start-Job -ScriptBlock { 
     param ( 
      [String]$Server, 
      [String]$Database, 
      [String]$ServerName 
     ) 


    if (Test-Connection -ComputerName $ServerName -quiet) 
      { 
       $sql = "UPDATE dbo.ServerList SET GoodPing = 1 WHERE GoodPing <> 1 AND ServerName = '$ServerName'" 
      } 
      else 
      { 
       $sql = "UPDATE dbo.ServerList SET GoodPing = 0 WHERE GoodPing <> 0 AND ServerName = '$ServerName'" 
      } 

      Invoke-SqlCmd -ServerInstance $Server -Database $Database -Query $sql 
    } -ArgumentList $Server, $Database, $srv.ServerName 
} 

y parece estar empezando múltiples puestos de trabajo ... pero mi mesa nunca se actualiza. Si elimino el material "Start-Job" y la lista argumentativa y uso $ srv.ServerName, entonces funciona de forma secuencial como lo hacía antes. ¿Algunas ideas? (Muchas gracias por cierto para todas las respuestas)

+0

Sí, lo siento, no tengo nada para probar esto. Si reemplaza 'Invoke-SqlCmd -ServerInstance $ Server -Database $ Database -Query $ sql' con' "Calling Invoke-SqlCmd ... {0} ServerInstance: {1} {2} Database: {3} {4} Query : {5} "-f [Medio ambiente] :: NewLine, $ Server, [Environment] :: NewLine, $ Database, [Environment] :: NewLine, $ sql | Out-File -FilePath "C: \ SqlCommands.txt" -Append' usted puede ver si se están pasando las variables correctas y reducir el problema a la llamada 'Invoke-SqlCmd'. –

+1

Invoke-SqlCmd es parte de SqlServerCmdletSnapin100 snapin. Tendrá que agregar -Para inicializar el parámetro de escritura a la llamada de inicio de trabajo. IMO sería más rápido/más fácil no cargar el snapin y simplemente use su propio código ADO.NET: $ con = "server = $ Server; database = $ Database; Integrated Security = sspi" $ cmd = "SELECCIONE ServerName FROM dbo.vwServerListActive "$ cmd.ExecuteNonQuery() –

+0

@Chad Ah sí, esa es la razón probable por la que esto no funciona. Colin, puedes intentar agregar el parámetro '-InitializationScript {Add-PSSnapin SqlServerCmdletSnapin100}' antes del parámetro '-ScriptBlock'. Edité mi publicación original. –

Cuestiones relacionadas