[英]Unexpected variable type returned by Receive-Job
I'm trying to execute the Invoke-Sqlcmd
command (from the SqlServer module ) to run a query as a different AD user.我正在尝试执行
Invoke-Sqlcmd
命令(来自SqlServer 模块)以作为不同的 AD 用户运行查询。 I know there's the -Credential
argument, but that doesn't seem to work.我知道有
-Credential
参数,但这似乎不起作用。
Thus, I thought using Start-Job
might be an option, as shown in the snippet below.因此,我认为使用
Start-Job
可能是一种选择,如下面的代码片段所示。
$username = 'dummy_domain\dummy_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
However, when looking at the variable type that the job returned, it isn't what I expected.但是,在查看作业返回的变量类型时,这不是我所期望的。
> $data.GetType().FullName
System.Management.Automation.PSObject
> $data.Tables[0].GetType().FullName
System.Collections.ArrayList
If I run the code in the ScriptBlock
directly, these are the variable types that PS returns:如果我直接运行
ScriptBlock
的代码,这些是PS返回的变量类型:
> $data.GetType().FullName
System.Data.DataSet
> $data.Tables[0].GetType().FullName
System.Data.DataTable
I tried casting the $data
variable to [System.Data.DataSet]
, which resulted in the following error message:我尝试将
$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"."
Questions:问题:
Invoke-Sqlcmd
command?Invoke-Sqlcmd
命令在不同的 AD 帐户下运行 SQL 查询?Receive-Job
?Receive-Job
时Receive-Job
正确/预期的变量类型? Update更新
When I run $data.Tables | Get-Member
当我运行
$data.Tables | Get-Member
$data.Tables | Get-Member
, one of the properties returned is: $data.Tables | Get-Member
,返回的属性之一是:
Tables Property Deserialized.System.Data.DataTableCollection {get;set;}
- Is there a way to get the correct/expected variable type to be returned when calling
Receive-Job
?有没有办法在调用
Receive-Job
时Receive-Job
正确/预期的变量类型?
Due to using a background job, you lose type fidelity : the objects you're getting back are method-less emulations of the original types.由于使用后台作业,您会失去类型保真度:您返回的对象是原始类型的无方法模拟。
Manually recreating the original types is not worth the effort and may not even be possible - though perhaps working with the emulations is enough.手动重新创建原始类型不值得付出努力,甚至可能无法实现 - 尽管使用仿真可能就足够了。
Update : As per your own answer , switching from working with System.DataSet
to System.DataTable
resulted in serviceable emulations for you.更新:根据您自己的回答,从使用
System.DataSet
切换到System.DataTable
为您提供了有用的模拟。 [1] [1]
See the bottom section for more information.有关更多信息,请参阅底部部分。
- Is there a better way to run SQL queries under a different AD account, using the Invoke-Sqlcmd command?
是否有更好的方法使用 Invoke-Sqlcmd 命令在不同的 AD 帐户下运行 SQL 查询?
You need an in-process invocation method in order to maintain type fidelity , but I don't think that is possible with arbitrary commands if you want to impersonate another user .您需要一个进程内调用方法来保持类型保真度,但我认为如果您想模拟另一个用户,则使用任意命令是不可能的。
For instance, the in-process (thread-based) alternative to Start-Job
- Start-ThreadJob
- doesn't have a -Credential
parameter.例如,进程内(基于线程)替代
Start-Job
- Start-ThreadJob
- 没有-Credential
参数。
Your best bet is therefore to try to make Invoke-SqlCmd
's -Credential
parameter work for you or find a different in-process way of running your queries with a given user's credentials.因此,您最好的选择是尝试使
Invoke-SqlCmd
的-Credential
参数为您工作,或者找到一种不同的进程内方式来使用给定用户的凭据运行查询。
Whenever PowerShell marshals objects across process boundaries , it employs XML-based serialization at the source, and deserialization at the destination , using a format known as CLI XML (Common Language Infrastructure XML).每当 PowerShell跨进程边界封送对象时,它都会在源使用基于 XML 的序列化,并在目标使用称为CLI XML (通用语言基础架构 XML)的格式进行反序列化。
This happens in the context of PowerShell remoting (eg, Invoke-Command
calls with the这发生在PowerShell远程处理的情况下(例如,
Invoke-Command
与要求-ComputerName
parameter) as well as in background jobs ( Start-Job
) and so-called mini-shells (which are implicitly used when you call the PowerShell CLI from inside PowerShell itself with a script block ; eg, powershell.exe { Get-Item / }
). -ComputerName
参数)以及后台作业( Start-Job
) 和所谓的mini-shell (当您使用脚本块从 PowerShell 内部调用 PowerShell CLI 时隐式使用它们;例如, powershell.exe { Get-Item / }
)。
This deserialization maintains type fidelity only for a limited set of known types , as specified in MS-PSRP, the PowerShell Remoting Protocol Specification .此反序列化仅为有限的一组已知类型保持类型保真度,如MS-PSRP(PowerShell 远程处理协议规范)中指定的那样。 That is, only instances of a fixed set of types are deserialized as their original type .
也就是说,只有一组固定类型的实例被反序列化为其原始类型。
Instances of all other types are emulated : list-like types become [System.Collections.ArrayList]
instances, dictionary types become [hasthable]
instances, and other types become method-less (properties-only) custom objects ( [pscustomobject]
instances) , whose .pstypenames
property contains the original type name prefixed with Deserialized.
模拟所有其他类型的实例:类列表类型变为
[System.Collections.ArrayList]
实例,字典类型变为[hasthable]
实例,其他类型变为无方法(仅属性)自定义对象( [pscustomobject]
实例) ,其.pstypenames
属性包含以Deserialized.
.pstypenames
前缀的原始类型名称Deserialized.
(eg, Deserialized.System.Data.DataTable
), as well as the equally prefixed names of the type's base types (inheritance hierarchy). (例如,
Deserialized.System.Data.DataTable
),以及类型的基类型(继承层次结构)的相同前缀名称。
Additionally, the recursion depth for object graphs of non - [pscustomobject]
instances is limited to 1
level - note that this includes instance of PowerShell custom classes , created with the class
keyword: That is, if an input object's property values aren't instance of well-known types themselves (the latter includes single-value-only types, including .NET primitive types such as [int]
, as opposed to types composed of multiple properties), they are replaced by their .ToString()
representations (eg, type System.IO.DirectoryInfo
has a .Parent
property that is another System.IO.DirectoryInfo
instance, which means that the .Parent
property value serializes as the .ToString()
representation of that instance, which is its full path string);此外,非
[pscustomobject]
实例的对象图的递归深度仅限于1
级- 请注意,这包括使用class
关键字创建的PowerShell 自定义类的实例:也就是说,如果输入对象的属性值不是实例众所周知的类型本身(后者包括单值类型,包括 .NET 原始类型,例如[int]
,而不是由多个属性组成的类型),它们被它们的.ToString()
表示替换(例如, 类型System.IO.DirectoryInfo
有一个.Parent
属性,它是另一个System.IO.DirectoryInfo
实例,这意味着.Parent
属性值序列化为该实例的.ToString()
表示,这是它的完整路径字符串); in short: Non-custom (scalar) objects serialize such that property values that aren't themselves instances of well-known types are replaced by their .ToString()
representation ;简而言之:非自定义(标量)对象序列化,使得本身不是众所周知类型实例的属性值被它们的
.ToString()
表示替换; see this answer for a concrete example.有关具体示例,请参阅此答案。
By contrast, explicit use of CLI XML serialization via Export-Clixml
defaults to a depth of 2
(you can specify a custom depth via -Depth
and you can similarly control the depth if you use the underlying System.Management.Automation.PSSerializer
type directly ).相比之下,通过明确使用CLI XML序列化的
Export-Clixml
默认的深度2
(你可以通过指定一个自定义的深度-Depth
,如果你使用的底层,你同样可以控制深度System.Management.Automation.PSSerializer
直接型)。
Depending on the original type, you may be able to reconstruct instances of the original type manually , but that is not guaranteed.根据原始类型,您也许能够手动重建原始类型的实例,但这并不能保证。 (You can get the original type's full name by calling
.pstypenames[0] -replace '^Deserialized\\.'
on a given custom object.) (您可以通过在给定的自定义对象上调用
.pstypenames[0] -replace '^Deserialized\\.'
来获取原始类型的全名。)
Depending on your processing needs, however, the emulations of the original objects may be sufficient.但是,根据您的处理需要,原始对象的模拟可能就足够了。
[1] Using System.DataTable
results in usable emulated objects, because you get a System.Collections.ArrayList
instance that emulates the table, and custom objects with the original property values for its System.DataRow
instances. [1] 使用
System.DataTable
产生可用的模拟对象,因为您会得到一个模拟表的System.Collections.ArrayList
实例,以及具有System.DataRow
实例的原始属性值的自定义对象。 The reason this works is that PowerShell has built-in logic to treat System.DataTable
implicitly as an array of its data rows , whereas the same doesn't apply to System.DataSet
.这样做的原因是 PowerShell 具有将
System.DataTable
隐式视为其数据行数组的内置逻辑,而这不适用于System.DataSet
。
I can't say for question 2 as I've never used the job commands but when it comes to running the Invoke-Sqlcmd
I always make sure that the account that runs the script has the correct access to run the SQL.我不能说问题 2,因为我从未使用过作业命令,但是在运行
Invoke-Sqlcmd
我总是确保运行脚本的帐户具有运行 SQL 的正确访问权限。
The plus to this is that you don't need to store the credentials inside the script, but is usually a moot point as the scripts are stored out of reach of most folks, although some bosses can be nit picky!这样做的好处是您不需要将凭据存储在脚本中,但通常是有争议的,因为脚本存储在大多数人无法触及的地方,尽管有些老板可能很挑剔!
Out of curiosity how do the results compare if you pipe them to Get-Member
?出于好奇,如果将结果通过管道传递给
Get-Member
,结果如何比较?
For those interested, below is the code I implemented.对于那些感兴趣的人,下面是我实现的代码。 Depending on whether or not
$credential
is passed, Invoke-Sqlcmd
will either run directly, or using a background job.根据是否传递
$credential
, Invoke-Sqlcmd
要么直接运行,要么使用后台作业。
I had to use -As DataTables
instead of -As DataSet
, as the latter seems to have issues with serialisation/deserialisation (see accepted answer for more info).我不得不使用
-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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.