Как вы выполняете запрос SQL Server из PowerShell?

161

Есть ли способ выполнить произвольный запрос на SQL Server, используя Powershell на моем локальном компьютере?


источник

Ответы:

166

Для других, которым нужно сделать это с помощью только стандартных .net и PowerShell (без дополнительных инструментов SQL), вот функция, которую я использую:

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

}

Я использовал это так долго, что я не знаю, кто написал, какие части, но это было извлечено из других примеров, но упрощено, чтобы быть ясным и просто то, что нужно без дополнительных зависимостей или функций.

Я использую и делюсь этим достаточно часто, чтобы превратить это в модуль сценариев на GitHub, чтобы вы теперь могли перейти в каталог модулей и выполнить его, git clone https://github.com/ChrisMagnuson/InvokeSQLи с этого момента invoke-sql будет автоматически загружаться при его использовании (при условии, что вы используете powershell v3 или новее).

Крис Магнусон
источник
не имеет значения распоряжение здесь или в PowerShell?
Маслоу
2
@Maslow Я не могу сказать наверняка, я знаю, что это нормально работает без удаления объектов, но если у вас есть один процесс powershell.exe, который будет вызывать его несколько раз в течение нескольких недель без закрытия, то это может в конечном итоге стать проблемой, но вы придется проверить это.
Крис Магнусон
1
Обратите внимание, что это вынуждает вас писать сценарии, которые могут быть уязвимы для атак внедрения SQL, если они зависят от чтения данных для запроса из источника, основанного на вводе пользователем.
Джоэл Коухорн
4
@AllTradesJack Google Sql Injection. Команда Invoke-Sql не имеет возможности включать параметры отдельно от текста команды. Это в значительной степени гарантирует, что вы использовали конкатенацию строк для построения запросов, и это большое нет-нет.
Джоэл Коухорн
1
Хорошо работает для меня. Для тех, кто интересуется: избавиться от объекта, просто добавить $connection.dispose()и т. Д. Я не знаю, имеет ли это какое-то значение
Nick.McDermaid
101

Вы можете использовать Invoke-Sqlcmdкомандлет

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

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

manojlds
источник
22
Кто-то должен упомянуть, что это может быть замечательно, если вы находитесь в контексте сервера sql, но не так много, если вы используете свою рабочую станцию ​​...
aikeru
10
Вы можете запустить его в любом месте, где установлены клиентские инструменты SQL Server (SSMS). Он отлично работает с любой рабочей станции, независимо от того, работает она на SQL Server или нет.
13
3
Используйте следующий импорт, чтобы иметь доступ к командлету: Import-Module "sqlps" -DisableNameChecking
xx1xx
1
Если вы все еще используете SQL 2008 R2, вам нужно использовать обходной
Винсент Де Смет
2
Invoke-SqlCmd - это бесконечный кошмар причудливых крайних случаев и противоречивого поведения. Почему он выводит столбцы иногда, а не иначе? Где мои сообщения об ошибках? Почему это на одном компьютере или нет на другом? Как мне это установить? Ответ на каждый вопрос хуже, чем последний.
Pxtl
28

Вот пример, который я нашел в этом блоге .

$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();

Предположительно, вы можете заменить другой оператор TSQL там, где он написан dbcc freeproccache.

ДМК
источник
1
Это решение работает для меня, однако, ExecuteNonQuery()вернулся к нулю в случае успеха, условие , что использование I является: if ($cmd.ExecuteNonQuery() -ne 0).
Гиксабель
Кажется, он возвращает количество затронутых строк. docs.microsoft.com/en-us/dotnet/api/…
NicolasW
27

Эта функция возвращает результаты запроса в виде массива объектов PowerShell, так что вы можете использовать их в фильтрах и легко получать доступ к столбцам:

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
источник
Почему это предпочтительнее, чем заполнять DataTable(см . Ответ Адама )?
Alroc
2
Вероятно, нет большой разницы, но SqlDataReaders обычно предпочитают, потому что они потребляют меньше ресурсов. Это вряд ли уместно здесь, но было бы неплохо вернуть реальные объекты вместо данных, которые можно использовать в выражениях foreach и where, не беспокоясь об источнике данных.
Макобриен
1
пример использования был бы хорош.
Эрик Шнайдер
Иногда не хватает звездочек
Фред Б.
13

Если вы хотите сделать это на локальном компьютере, а не в контексте SQL-сервера, я бы использовал следующее. Это то, что мы используем в моей компании.

$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

Просто заполните переменные $ ServerName , $ DatabaseName и $ Query, и все будет хорошо.

Я не знаю , как мы первоначально обнаружили это, но есть что - то очень похоже здесь .

Адам
источник
12
Invoke-Sqlcmd -Query "sp_who" -ServerInstance . -QueryTimeout 3
arnav
источник
он покажет номер соединения в sql, используемый командой powershell
arnav
0

Чтобы избежать SQL-инъекций с параметрами varchar, вы можете использовать


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
источник