接收作业返回的意外变量类型



我正在尝试执行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"."

问题:

  1. 有没有更好的方法可以使用Invoke-Sqlcmd命令在不同的 AD 帐户下运行 SQL 查询?
  2. 有没有办法在调用Receive-Job时返回正确/预期的变量类型?

更新

当我运行$data.Tables | Get-Member时,返回的属性之一是:

Tables  Property  Deserialized.System.Data.DataTableCollection {get;set;}    
  1. 有没有办法在调用Receive-Job时返回正确/预期的变量类型?

由于使用后台作业,您将失去类型保真度:您返回的对象是原始类型的无方法模拟

手动重新创建原始类型不值得付出努力,甚至可能是不可能的 - 尽管也许使用仿真就足够了。

更新:根据您自己的答案,从使用System.DataSet切换到System.DataTable为您提供了可用的仿真。[1]

有关详细信息,请参阅底部部分。

  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,结果如何比较?

对于那些感兴趣的人,下面是我实现的代码。根据是否传递$credentialInvoke-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
}

最新更新