我正在尝试执行Invoke-Sqlcmd
命令(从 SqlServer 模块)以不同的 AD 用户身份运行查询。我知道有-Credential
论点,但这似乎行不通。
因此,我认为使用Start-Job
可能是一种选择,如下面的代码片段所示。
$username = 'dummy_domaindummy_user'
$userpassword = 'dummy_pwd' | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential ($username, $password)
$job = Start-Job -ScriptBlock {Import-Module SqlServer; Invoke-Sqlcmd -query "exec sp_who" -ServerInstance 'dummy_mssql_server' -As DataSet} -Credential $credential
$data = Receive-Job -Job $job -Wait -AutoRemoveJob
但是,在查看作业返回的变量类型时,这不是我所期望的。
> $data.GetType().FullName
System.Management.Automation.PSObject
> $data.Tables[0].GetType().FullName
System.Collections.ArrayList
如果我直接在ScriptBlock
中运行代码,这些是 PS 返回的变量类型:
> $data.GetType().FullName
System.Data.DataSet
> $data.Tables[0].GetType().FullName
System.Data.DataTable
我尝试将$data
变量转换为[System.Data.DataSet]
,这导致了以下错误消息:
Cannot convert value "System.Data.DataSet" to type "System.Data.DataSet".
Error: "Cannot convert the "System.Data.DataSet" value of type
"Deserialized.System.Data.DataSet" to type "System.Data.DataSet"."
问题:
- 有没有更好的方法可以使用
Invoke-Sqlcmd
命令在不同的 AD 帐户下运行 SQL 查询? - 有没有办法在调用
Receive-Job
时返回正确/预期的变量类型?
更新
当我运行$data.Tables | Get-Member
时,返回的属性之一是:
Tables Property Deserialized.System.Data.DataTableCollection {get;set;}
- 有没有办法在调用
Receive-Job
时返回正确/预期的变量类型?
由于使用后台作业,您将失去类型保真度:您返回的对象是原始类型的无方法模拟。
手动重新创建原始类型不值得付出努力,甚至可能是不可能的 - 尽管也许使用仿真就足够了。
更新:根据您自己的答案,从使用System.DataSet
切换到System.DataTable
为您提供了可用的仿真。[1]
有关详细信息,请参阅底部部分。
- 是否有更好的方法可以使用 Invoke-Sqlcmd 命令在不同的 AD 帐户下运行 SQL 查询?
您需要一个进程内调用方法来保持类型保真度,但如果您想模拟其他用户,我认为使用任意命令是不可能的。
例如,Start-Job
的进程内(基于线程)替代项 -Start-ThreadJob
- 没有-Credential
参数。
因此,最好的办法是尝试使Invoke-SqlCmd
的-Credential
参数为您工作,或者找到一种不同的进程内方式来使用给定用户的凭据运行查询。
后台作业/远程处理/迷你外壳中对象的序列化和反序列化:
每当 PowerShell跨进程边界封送对象时,它都会使用称为CLIXML(公共语言基础结构 XML)的格式在源使用基于 XML 的序列化,在目标使用反序列化。
这发生在PowerShell远程处理的上下文中(例如,使用-ComputerName
参数的Invoke-Command
调用)以及后台作业(Start-Job
)和所谓的迷你shell(当您使用脚本块从PowerShell本身内部调用PowerShell CLI时隐式使用;例如,powershell.exe { Get-Item / }
)。
此反序列化仅对一组有限的已知类型保持类型保真度,如 MS-PSRP(PowerShell 远程处理协议规范)中指定的那样。也就是说,只有一组固定类型的实例才会反序列化为其原始类型。
模拟所有其他类型的实例:类似列表的类型成为[System.Collections.ArrayList]
实例,字典类型成为[hasthable]
实例,其他类型的成为无方法(仅属性)自定义对象([pscustomobject]
实例),其.pstypenames
属性包含前缀为Deserialized.
的原始类型名称(例如,Deserialized.System.Data.DataTable
),以及类型基类型的前缀相同的名称(继承层次结构)。
此外,非[pscustomobject]
实例的对象图的递归深度仅限于1
级别- 请注意,这包括使用class
关键字创建的 PowerShell 自定义类的实例: 也就是说,如果输入对象的属性值本身不是已知类型的实例(后者包括仅单值类型,包括 .NET 基元类型,如[int]
, 与由多个属性组成的类型相反),它们被替换为它们的.ToString()
表示形式(例如,类型System.IO.DirectoryInfo
具有另一个System.IO.DirectoryInfo
实例的.Parent
属性,这意味着.Parent
属性值序列化为该实例的.ToString()
表示形式,这是其完整路径字符串);简而言之:非自定义(标量)对象进行序列化,以便将本身不是已知类型实例的属性值替换为其.ToString()
表示形式;有关具体示例,请参阅此答案。
相比之下,通过Export-Clixml
显式使用 CLI XML 序列化默认为深度2
(您可以通过-Depth
指定自定义深度,如果直接使用基础System.Management.Automation.PSSerializer
类型,也可以类似地控制深度)。
根据原始类型,您可能能够手动重建原始类型的实例,但不能保证这一点。(可以通过对给定的自定义对象调用.pstypenames[0] -replace '^Deserialized.'
来获取原始类型的全名。
但是,根据您的处理需求,原始对象的模拟可能就足够了。
[1] 使用System.DataTable
会产生可用的模拟对象,因为您将获得一个模拟表的System.Collections.ArrayList
实例,以及具有其System.DataRow
实例的原始属性值的自定义对象。这样做的原因是PowerShell具有内置逻辑,可以将System.DataTable
隐式视为其数据行数组,而这不适用于System.DataSet
。
我不能说问题 2,因为我从未使用过作业命令,但在运行Invoke-Sqlcmd
时,我始终确保运行脚本的帐户具有运行 SQL 的正确访问权限。
这样做的好处是,您不需要将凭据存储在脚本中,但通常是一个有争议的问题,因为脚本存储在大多数人无法访问的地方,尽管有些老板可能会挑剔!
出于好奇,如果将它们管道输送到Get-Member
,结果如何比较?
对于那些感兴趣的人,下面是我实现的代码。根据是否传递$credential
,Invoke-Sqlcmd
将直接运行或使用后台作业运行。
我不得不使用-As DataTables
而不是-As DataSet
,因为后者似乎在序列化/反序列化方面存在问题(有关更多信息,请参阅接受的答案)。
function Exec-SQL($server, $database, $query, $credential) {
$sqlData = @()
$scriptBlock = {
Param($params)
Import-Module SqlServer
return Invoke-Sqlcmd -ServerInstance $params.server -Database $params.database -query $params.query -As DataTables -OutputSqlErrors $true
}
if ($PSBoundParameters.ContainsKey("credential")) {
$job = Start-Job -ScriptBlock $scriptBlock -Credential $credential -ArgumentList $PSBoundParameters
$sqlData = Receive-Job -Job $job -Wait -AutoRemoveJob
} else {
$sqlData = & $scriptBlock -params $PSBoundParameters
}
return $sqlData
}