PowerShell中Invoke-Sqlcmd的Unicode支持

12

PowerShell的sqlps模块提供了从PowerShell中访问SQL Server所需的核心支持,其Invoke-Sqlcmd cmdlet是执行文本查询或SQL脚本文件的主要工具(类似于非PowerShell的sqlcmd实用程序)。我最近尝试了一些实验来确认Invoke-Sqlcmd处理Unicode的能力,并得到了一些令人惊讶的结果。

我从这个简单的脚本文件开始(命名为unicode.sql):

CREATE TABLE #customers

( [IdCust] int,
  [FirstName] nvarchar(25),
  [SurName] nvarchar(25)
);
INSERT INTO #customers VALUES (4, N'Hans', N'Grüßner')
SELECT * FROM #customers;
DROP TABLE #customers;

请注意,姓氏中可能包含一些德国名字中常见的Unicode字符。


结果

SQL Server Management Studio:在输出到网格或文本时正确呈现,例如。

IdCust      FirstName                 Surname
----------- ------------------------- -------------------------
4           Hans                      Grüßner

sqlcmd 工具: 无论是从 DOS shell 运行还是从 PowerShell 运行,都可以正确渲染,例如:

C:\> sqlcmd -S .\SQLEXPRESS -i unicode.sql

IdCust      FirstName                 Surname
----------- ------------------------- -------------------------
          4 Hans                      Grüßner

PowerShell Invoke-Sqlcmd:渲染出现问题(无论是作为下面显示的文本输出还是通过管道传入Out-Gridview):

PS> Invoke-Sqlcmd -Server .\sqlexpress -InputFile unicode.sql

IdCust FirstName           Surname
------ ---------           -------
     4 Hans                Gr??ner

Invoke-Sqlcmd的MSDN文档仅在比较其命令行开关与sqlcmd时才简单提到Unicode,表明后者具有用于输出Unicode的-u选项(甚至在我的实验中不需要),而Invoke-Sqlcmd没有等效的参数。

我通过广泛的网络搜索没有找到任何关于此点的信息,但我仍然希望这在某种程度上是我的用户错误。在PowerShell中使用Invoke-Sqlcmd检索输入数据时是否有保留数据的方法?


我认为你应该更改你正在使用的SQL Server的字符编码。 - Lajos Arpad
你的PowerShell $OutputEncoding设置是什么?这是一个有趣的问题,我期待有时间来测试一下。 - Bruce
$OutputEncoding 被设置为 US-ASCII。我尝试将其设置为 [Text.Encoding]::Unicode[Text.Encoding]::utf8,但两种情况下结果都没有改变。 - Michael Sorens
2个回答

16

更新:我在另一台计算机上测试了invoke-sqlcmd,它可以正常工作,所以也许后面的内容不适用...

更新2:只有在使用 -Query 参数执行时,-inputfile 才似乎存在问题,invoke-sqlcmd 正常工作。

据我所知,这与 ADO.NET DataTable 在转换字符串时有关。当您使用 ExecuteScaler 或 ExecuteReader 时,它可以正常工作。当然,这无法解决 invoke-sqlcmd 的问题,但可以解释为什么会出现此问题:

$server = "$env:computername\sql1"
$database = "tempdb"
$query = @"
CREATE TABLE #customers

(     [SurName] nvarchar(25)
);
INSERT INTO #customers VALUES (N'Grüßner')
SELECT * FROM #customers;
"@


$connection=new-object System.Data.SqlClient.SQLConnection
$connection.ConnectionString="Server={0};Database={1};Integrated Security=True" -f $server,$database
$command=new-object system.Data.SqlClient.SqlCommand($query,$connection)
$connection.Open()
$command.ExecuteScalar()
$connection.Close()

更新 3 文件的编码似乎是关键。查看[System.IO.File]::ReadAllText,MSDN文档指出它只能检测UTF-8或UTF-32编码。http://msdn.microsoft.com/en-us/library/ms143369(v=vs.90).aspx

如果我使用UTF-8保存.sql文件,则使用-inputfile参数可行。你可以在SSMS中选择UTF-8来保存.sql文件,但这里是一些PowerShell代码来检查和更改编码。你需要从http://poshcode.org/2075获取Get-FileEncoding.ps1。

. .\Get-FileEncoding.ps1 
Get-FileEncoding -Path E:\bin\unicode.sql

$query = get-content E:\bin\unicode.sql
$query= $query -join "`n"
$query | Out-File -FilePath e:\bin\unicode.sql -Encoding UTF8 -force

Get-FileEncoding -Path E:\bin\unicode.sql

谢谢提供信息,Chad。但我有几个问题。当我尝试运行你的代码时,会出现“关键字'VALUES'附近的语法不正确”的错误——即使查询在SSMS中单独运行也没有问题。有什么方法可以避免这个错误吗?...所以我尝试将查询设置为select N'Grüßner' as surname,但显然这不是一个有效的等价物,因为它甚至可以在Invoke-Sqlcmd中工作。 - Michael Sorens
使用 Here-Strings 或称为字面字符串时,您需要通过 "@ 的方法将其单独运行并复制粘贴 $query。此外,最好使用 Powershell_ise,这样就不用担心了。正如您所说,这可能无法构成有效的测试。 - Chad Miller
请参见update2。当使用-Query参数时,您的代码可以正常工作,但在使用-inputfile参数时则不行。 - Chad Miller
由于您将问题隔离到“-InputFile”,我将输入与输出分开。首先,我创建了一个非临时表并插入了Unicode名称。然后,“select * from uni_customers”可以正确地检索它,无论是使用“-Query”还是“-InputFile”,因此输出正常。我清空了输入测试的表格,然后进行了两个单独的插入。现在,“select *”返回两行数据,一行好的(使用“-Query”插入)和一行坏的(使用“-InputFile”插入)。结论:使用PowerShell从数据库检索的数据是有效的;只有在插入文件时才会出现问题。 - Michael Sorens
很好的发现。根据你的观察,我进一步缩小了范围。如果.sql文件使用UTF-8或UTF-32编码,则输入文件将起作用。我已发布了一些代码和说明。 - Chad Miller
显示剩余2条评论

0

在执行 SQLCMD 时,必须指定编码。

EXEC xp_cmdshell 'for %f in ("{Dir}*.sql") do sqlcmd -S {Server} -U {username} -P {password} -d {database} -i "%f" -b -f 65001'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接