Ошибка из-за передачи параметров в файл SQL с использованием Invoke-SQLCmd

#sql-server #powershell

#sql-сервер #powershell

Вопрос:

У меня есть простой SQL-файл, который создает новую базу данных на основе 3 отправленных входных параметров: dbName, datafileName и LogFileName

Но, когда я вызываю этот скрипт через powershell, вызывая командлет Invoke-Sqlcmd, я получаю следующую ошибку:

Неправильный синтаксис возле 'CommonDB'.
Неправильный синтаксис рядом с ''.
Неправильный синтаксис возле 'CommonDB'.
Неправильный синтаксис рядом с 'E:'.
Метка 'E' уже объявлена. Имена меток должны быть уникальными в пакете запросов или хранимой процедуре.
Неправильный синтаксис рядом с 'E:'.
Метка 'E' уже объявлена. Имена меток должны быть уникальными в пакете запросов или хранимой процедуре.
Неправильный синтаксис возле 'CommonDB'.
Метка 'E' уже объявлена. Имена меток должны быть уникальными в пакете запросов или хранимой процедуре.
Метка 'E' уже объявлена. Имена меток должны быть уникальными в пакете запросов или хранимой процедуре.
В строке: 1 символ: 1
  Invoke-Sqlcmd -ServerInstance localhostsql2012 -Мастер базы данных -Использование...
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   CategoryInfo : недопустимая операция: (:) [Invoke-Sqlcmd], исключение SqlPowerShellSqlExecutionException
   FullyQualifiedErrorId: ошибка SqlError, Microsoft.SQLServer.Management.PowerShell.GetScriptCommand

Моя версия Powershell:

Основная Незначительная ревизия сборки
----- ----- ----- --------
5 1 16299 1004

Это мой файл CreateDatabase.sql:

 USE MASTER
GO

SET QUOTED_IDENTIFIER ON;
GO

DECLARE 
    @DbName nvarchar(50),
    @sql nvarchar(max),
    @datafileName nvarchar(500),
    @logfileName nvarchar(500)


SET @sql = N''

SELECT @DbName = N'$(dbName)'
SELECT @datafileName = N'$(datafileName)'
SELECT @logfileName = N'$(logfileName)'

IF NOT EXISTS (SELECT * FROM master.sys.databases WHERE name = @DbName)
BEGIN
SELECT @sql = 
    N'CREATE DATABASE "'   @DbName   N'"'
      N' ON PRIMARY ( NAME = ['    @DbName    '] ,'
      N' FILENAME = ['    @datafileName    '],'
      N' SIZE = 1GB , MAXSIZE = UNLIMITED, FILEGROWTH = 512MB  )'
      N' LOG ON ( NAME = ['   @DbName   '_log] ,'
      N' FILENAME = ['    @logfileName   '], SIZE = 100MB , FILEGROWTH = 10% )'
END

IF (@sql <> N'')
EXEC (@sql)
GO

SET QUOTED_IDENTIFIER OFF;
GO
  

Это содержимое моего файла PS1:

 $params=@("dbName='CommonDB'", "datafileName='E:my filessql.2012dataCommonDb.mdf'", "logfileName='E:my filessql.2012dataCommonDb_log.ldf'")

Invoke-Sqlcmd -ServerInstance localhostsql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .CreateDatabase.sql -Variable $params
  

Но, если я запускаю это из командной строки PS, все работает просто отлично:

 PS E:My Scripts> Invoke-Sqlcmd -ServerInstance localhostsql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .CreateDatabase.sql -Variable dbName='CommonDB', datafileName='E:my filessql.2012dataCommonDb.mdf', logfileName='E:my filessql.2012dataCommonDb_log.ldf'
  

Ответ №1:

Попробуйте :

 $params=@{"dbName"="CommonDB"; "datafileName"="E:my filessql.2012dataCommonDb.mdf"; "logfileName"="E:my filessql.2012dataCommonDb_log.ldf"}
  

или

 $dbName = "CommonDB"
$datafileName = "E:my filessql.2012dataCommonDb.mdf"
$logfileName = "E:my filessql.2012dataCommonDb_log.ldf"
Invoke-Sqlcmd -ServerInstance localhostsql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .CreateDatabase.sql -Variable dbName=$dbName, datafileName=$datafileName, logfileName=$logfileName
  

Комментарии:

1. спасибо за ответ. Вариант 1 не будет работать, поскольку @params это массив строк, и вы не можете иметь пары имя-значение в массиве строк. Вариант 2 работает в этом конкретном случае. Но я хочу, чтобы мой скрипт был универсальным. Он должен иметь возможность вызывать другие файлы .sql, имена и значения параметров которых динамически изменяются.

Ответ №2:

Хорошо, я заставил эту штуку работать в общем виде, сгенерировав скрипт, необходимый для вызова-sqlcmd. Написал функцию, которая принимает файл сценария и его параметры и генерирует ScriptBlock из того же:

 <#
.SYNOPSIS
#

.DESCRIPTION
This function invokes sql in a .sql file and passes the parameters to it as needed. 

.PARAMETER script_file
 $script_file - The relative or absolute path to the sql file that needs to be executed
 $parameter_names - a list of string containing the names of the parameters which need to be passed to the sql file

.EXAMPLE
For example if there is a SQL file which takes 3 parameters: dbName, datafileName, and logfileName, whose values are: 
"CommonDB", "E:my filessql.2012dataCommonDb.mdf", and "E:my filessql.2012dataCommonDb_log.ldf" repectively, 
this function first generates the following ScriptBlock for the same and then executes it by calling the Invoke-Command cmdlet: 

$dbName = "CommonDB"
$datafileName = "E:my filessql.2012dataCommonDb.mdf"
$logfileName = "E:my filessql.2012dataCommonDb_log.ldf"
Invoke-Sqlcmd -ServerInstance localhostsql2012 -Database master -Username sa -Password xxxx -ErrorAction Stop -InputFile .CreateDatabase.sql -Variable dbName=$dbName, datafileName=$datafileName, logfileName=$logfileName

#>

Function Invoke-SQLFile( [string] $script_file, [System.Collections.Generic.List[string]]$parameter_names) {

    #Assuming that $serverInstance, $db, $username, and $password are global variables
    $invokeSql_Script = "Invoke-Sqlcmd -ServerInstance `"$serverInstance`" -Database `"$db`" -Username `"$username`" -Password `"$password`" -ErrorAction Stop -InputFile `"$script_file`" "

    $scriptStr = ""

    #The script file may or may not accept parameters
    if(($null -ne $parameter_names) -and ($parameter_names.Count -gt 0)){
        $invokeSqlVariableStr = ""
        foreach($param in $parameter_names){
            #There would be a global variable with the same name as the parameter_name 
            # e.g. If the param name is "databaseName", there should be a global variable called $databaseName
            $paramVal = Get-Variable -Name $param | Select-Object -Property Value 
            $scriptStr = "$scriptStr `$$param = `"$($paramVal.Value)`";`r`n" 
            $invokeSqlVariableStr = "$invokeSqlVariableStr $param=`"`$$param`", "
        }
        #remove the trailing ".," from the string
        $invokeSqlVariableStr = $invokeSqlVariableStr -replace ".{2}$"
        $scriptStr = "$scriptStr $invokeSql_Script -Variable $invokeSqlVariableStr"
    }
    else {
        #if no parameters need to be passed, then the $invokeSql_Script is good enough
        $scriptStr = $invokeSql_Script
    }
    $scriptBlock = [System.Management.Automation.ScriptBlock]::Create($scriptStr)
    Invoke-Command -ScriptBlock $scriptBlock    
}
  

Спасибо @rAJ за эту идею.

Тем не менее, я все еще не знаю, почему мой первоначальный скрипт не удался. И указатели там приветствуются.