¿Cómo ejecuta una consulta de SQL Server desde PowerShell?

161

¿Hay alguna manera de ejecutar una consulta arbitraria en un servidor SQL utilizando Powershell en mi máquina local?


fuente

Respuestas:

166

Para otros que necesitan hacer esto solo con stock .net y PowerShell (sin herramientas SQL adicionales instaladas), esta es la función que uso:

function Invoke-SQL {
    param(
        [string] $dataSource = ".\SQLEXPRESS",
        [string] $database = "MasterData",
        [string] $sqlCommand = $(throw "Please specify a query.")
      )

    $connectionString = "Data Source=$dataSource; " +
            "Integrated Security=SSPI; " +
            "Initial Catalog=$database"

    $connection = new-object system.data.SqlClient.SQLConnection($connectionString)
    $command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)
    $connection.Open()

    $adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
    $dataset = New-Object System.Data.DataSet
    $adapter.Fill($dataSet) | Out-Null

    $connection.Close()
    $dataSet.Tables

}

He estado usando esto durante tanto tiempo que no sé quién escribió qué partes, pero esto se destiló de los ejemplos de otros, pero se simplificó para ser claro y lo que se necesita sin dependencias o características adicionales.

Utilizo y comparto esto con suficiente frecuencia que lo he convertido en un módulo de script en GitHub para que ahora pueda ir al directorio de módulos y ejecutarlo git clone https://github.com/ChrisMagnuson/InvokeSQLy, a partir de ese momento, invoke-sql se cargará automáticamente cuando vaya a usarlo (suponiendo estás usando powershell v3 o posterior).

Chris Magnuson
fuente
¿La eliminación no importa aquí o en PowerShell?
Maslow
2
@Maslow No podría decir con certeza, sé que esto funciona bien sin deshacerse de los objetos, pero si tiene un solo proceso powershell.exe que lo llamará varias veces durante semanas sin cerrar, podría eventualmente ser un problema, pero usted tendría que probar eso.
Chris Magnuson
1
Tenga en cuenta que esto lo obliga a escribir secuencias de comandos que pueden ser vulnerables a los ataques de inyección SQL, si dependen de la lectura de datos para la consulta desde una fuente que depende de la entrada del usuario.
Joel Coehoorn
44
@AllTradesJack Google Sql Injection. El comando Invoke-Sql no tiene una forma de incluir parámetros separados del texto del comando. Esto prácticamente garantiza que usaste la concatenación de cadenas para generar las consultas, y eso es un gran no-no.
Joel Coehoorn
1
Funciona bien para mi Para cualquiera que se pregunte, para deshacerse de un objeto, simplemente agregue, $connection.dispose()etc. No sé si hace alguna diferencia
Nick.McDermaid
101

Puedes usar el Invoke-Sqlcmdcmdlet

Invoke-Sqlcmd -Query "SELECT GETDATE() AS TimeOfQuery;" -ServerInstance "MyComputer\MyInstance"

http://technet.microsoft.com/en-us/library/cc281720.aspx

manojlds
fuente
22
Alguien debería hablar de esto puede ser grande si usted está en el contexto del servidor SQL, pero no tanto si está utilizando la estación de trabajo ...
aikeru
10
Puede ejecutar esto en cualquier lugar donde estén instaladas las herramientas de cliente de SQL Server (SSMS). Funciona bien desde cualquier estación de trabajo, ya sea que ejecute SQL Server o no.
alroc
3
Use la siguiente importación para tener el cmdlet disponible: Import-Module "sqlps" -DisableNameChecking
xx1xx
1
Si todavía está en SQL 2008 R2, necesita usar un módulo alternativo
Vincent De Smet
2
Invoke-SqlCmd es una pesadilla interminable de casos extremos extraños y comportamiento inconsistente. ¿Por qué genera columnas a veces y no otras? ¿Dónde están mis mensajes de error? ¿Por qué está en una computadora o no en otra? ¿Como lo instalo? La respuesta a cada pregunta es peor que la anterior.
Pxtl
28

Aquí hay un ejemplo que encontré en este blog .

$cn2 = new-object system.data.SqlClient.SQLConnection("Data Source=machine1;Integrated Security=SSPI;Initial Catalog=master");
$cmd = new-object system.data.sqlclient.sqlcommand("dbcc freeproccache", $cn2);
$cn2.Open();
if ($cmd.ExecuteNonQuery() -ne -1)
{
    echo "Failed";
}
$cn2.Close();

Presumiblemente, podría sustituir una declaración TSQL diferente donde dice dbcc freeproccache.

dmc
fuente
1
Esta solución trabajado para mí, sin embargo, ExecuteNonQuery()devuelve cero en caso de éxito, la condición de que el uso que es: if ($cmd.ExecuteNonQuery() -ne 0).
Gixabel
Parece que devuelve el número de líneas impactadas. docs.microsoft.com/en-us/dotnet/api/…
NicolasW
27

Esta función devolverá los resultados de una consulta como una matriz de objetos de PowerShell para que pueda usarlos en filtros y acceder a columnas fácilmente:

function sql($sqlText, $database = "master", $server = ".")
{
    $connection = new-object System.Data.SqlClient.SQLConnection("Data Source=$server;Integrated Security=SSPI;Initial Catalog=$database");
    $cmd = new-object System.Data.SqlClient.SqlCommand($sqlText, $connection);

    $connection.Open();
    $reader = $cmd.ExecuteReader()

    $results = @()
    while ($reader.Read())
    {
        $row = @{}
        for ($i = 0; $i -lt $reader.FieldCount; $i++)
        {
            $row[$reader.GetName($i)] = $reader.GetValue($i)
        }
        $results += new-object psobject -property $row            
    }
    $connection.Close();

    $results
}
mcobrien
fuente
¿Por qué es esto preferible a llenar un DataTable(ver la respuesta de Adam )?
alroc
2
Probablemente no haya una gran diferencia, pero generalmente se prefieren los SqlDataReaders porque consumen menos recursos. Es probable que eso no sea relevante aquí, pero es bueno recuperar objetos reales en lugar de una tabla de datos que puede usar en foreach y where cláusulas sin preocuparse por la fuente de los datos.
mcobrien
1
Un ejemplo de uso sería bueno.
Eric Schneider
A veces no hay suficientes estrellas
Fred B
13

Si desea hacerlo en su máquina local en lugar de hacerlo en el contexto del servidor SQL, usaría lo siguiente. Es lo que usamos en mi empresa.

$ServerName = "_ServerName_"
$DatabaseName = "_DatabaseName_"
$Query = "SELECT * FROM Table WHERE Column = ''"

#Timeout parameters
$QueryTimeout = 120
$ConnectionTimeout = 30

#Action of connecting to the Database and executing the query and returning results if there were any.
$conn=New-Object System.Data.SqlClient.SQLConnection
$ConnectionString = "Server={0};Database={1};Integrated Security=True;Connect Timeout={2}" -f $ServerName,$DatabaseName,$ConnectionTimeout
$conn.ConnectionString=$ConnectionString
$conn.Open()
$cmd=New-Object system.Data.SqlClient.SqlCommand($Query,$conn)
$cmd.CommandTimeout=$QueryTimeout
$ds=New-Object system.Data.DataSet
$da=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
[void]$da.fill($ds)
$conn.Close()
$ds.Tables

Simplemente complete las variables $ ServerName , $ DatabaseName y $ Query y estará listo.

No estoy seguro de cómo nos encontramos originalmente esto, pero hay algo muy similar aquí .

Adán
fuente
12
Invoke-Sqlcmd -Query "sp_who" -ServerInstance . -QueryTimeout 3
arnav
fuente
mostrará el número de conexiones en sql utilizadas por el comando powershell
arnav el
0

Para evitar la inyección SQL con parámetros varchar, puede usar


function sqlExecuteRead($connectionString, $sqlCommand, $pars) {

        $connection = new-object system.data.SqlClient.SQLConnection($connectionString)
        $connection.Open()
        $command = new-object system.data.sqlclient.sqlcommand($sqlCommand, $connection)

        if ($pars -and $pars.Keys) {
            foreach($key in $pars.keys) {
                # avoid injection in varchar parameters
                $par = $command.Parameters.Add("@$key", [system.data.SqlDbType]::VarChar, 512);
                $par.Value = $pars[$key];
            }
        }

        $adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
        $dataset = New-Object System.Data.DataSet
        $adapter.Fill($dataset) | Out-Null
        $connection.Close()
        return $dataset.tables[0].rows

    }

    $connectionString = "..."
    $sql = "select top 10 Message, TimeStamp, Level from dbo.log "+
        "where Message = @MSG and Level like @LEVEL ";
    $pars = @{
        MSG = 'this is a test from powershell'; 
        LEVEL = 'aaa%';
    };
    sqlExecuteRead $connectionString $sql $pars
Piero
fuente