简体   繁体   English

Receive-Job 返回的意外变量类型

[英]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:问题:

  1. Is there a better way to run SQL queries under a different AD account, using the Invoke-Sqlcmd command?是否有更好的方法使用Invoke-Sqlcmd命令在不同的 AD 帐户下运行 SQL 查询?
  2. Is there a way to get the correct/expected variable type to be returned when calling Receive-Job ?有没有办法在调用Receive-JobReceive-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;}    
  1. Is there a way to get the correct/expected variable type to be returned when calling Receive-Job ?有没有办法在调用Receive-JobReceive-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.有关更多信息,请参阅底部部分。

  1. 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参数为您工作,或者找到一种不同的进程内方式来使用给定用户的凭据运行查询。


Serialization and deserialization of objects in background jobs / remoting / mini-shells:后台作业/远程处理/迷你壳中对象的序列化和反序列化:

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.根据是否传递$credentialInvoke-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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM