【问题标题】:Unexpected variable type returned by Receive-JobReceive-Job 返回的意外变量类型
【发布时间】:2020-03-29 04:24:43
【问题描述】:

我正在尝试执行Invoke-Sqlcmd 命令(来自SqlServer module)以其他 AD 用户身份运行查询。我知道有 -Credential 参数,但这似乎不起作用。

因此,我认为使用Start-Job 可能是一种选择,如下面的sn-p 所示。

$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

但是,当查看作业返回的变量类型时,它不是我所期望的。

> $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. 有没有更好的方法在不同的 AD 帐户下运行 SQL 查询,使用 Invoke-Sqlcmd 命令?
  2. 有没有办法在调用Receive-Job 时获得正确/预期的变量类型?

更新

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

Tables  Property  Deserialized.System.Data.DataTableCollection {get;set;}    

【问题讨论】:

  • 预期 - 作业在不同的进程中运行,因此数据必须在返回给您之前进行序列化和反序列化
  • @MathiasR.Jessen 这是否意味着在反序列化对象时变量类型出了问题?有没有办法完全按照以前的方式反序列化对象?

标签: sql-server powershell


【解决方案1】:

我不能说问题 2,因为我从未使用过作业命令,但是在运行 Invoke-Sqlcmd 时,我始终确保运行脚本的帐户具有运行 SQL 的正确访问权限。

这样做的好处是您不需要将凭据存储在脚本中,但这通常是一个有争议的问题,因为脚本存储在大多数人无法触及的地方,尽管有些老板可能会挑剔!

出于好奇,如果将结果通过管道传输到Get-Member,结果如何比较?

【讨论】:

    【解决方案2】:
    1. 有没有办法在调用Receive-Job 时获得正确/预期的变量类型?

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

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

    更新:作为per your own answer,从使用System.DataSet 切换到System.DataTable 为您提供了可用的模拟。[1]

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

    1. 有没有更好的方法在不同的 AD 帐户下使用 Invoke-Sqlcmd 命令运行 SQL 查询?

    你需要一个进程内调用方法来保持类型保真度,但如果你想,我认为使用任意命令是不可能的>冒充另一个用户

    例如,Start-Job - Start-ThreadJob 的进程内(基于线程)替代方案 - 没有 -Credential 参数。

    因此,您最好的办法是尝试使 Invoke-SqlCmd-Credential 参数为您工作,或者找到一种不同的进程内方式来使用给定用户的凭据运行查询。


    后台作业/远程处理/迷你shell中对象的序列化和反序列化:

    每当 PowerShell 进程边界编组对象时,它都会在源头采用基于XML的序列化,并反序列化 在目的地,使用称为 CLI XML(通用语言基础结构 XML)的格式。

    这发生在 PowerShell 远程处理(例如,使用
    -ComputerName 参数调用Invoke-Command)以及后台作业(@ 987654324@) 和所谓的 mini-shells(当您从 PowerShell 本身内部调用 PowerShell CLI 时隐式使用它们带有脚本块;例如,powershell.exe { Get-Item / }) .

    这种反序列化只为已知类型的有限集合维护类型保真度,如MS-PSRP, the PowerShell Remoting Protocol Specification 中所指定。也就是说,只有一组固定类型的实例被反序列化为作为它们的原始类型

    所有其他类型的实例被模拟:类似列表的类型变成[System.Collections.ArrayList] 实例,字典类型变成[hasthable] 实例,其他类型变成无方法(仅限属性)自定义对象[pscustomobject] 实例),其.pstypenames 属性包含以Deserialized. 为前缀的原始类型名称(例如,Deserialized.System.Data.DataTable) ,以及类型的 base 类型(继承层次结构)的前缀相同的名称。

    此外,-[pscustomobject] 实例的对象图的递归深度仅限于1 级别 - 请注意,这包括 PowerShell custom classes 的实例,使用 class 关键字创建:也就是说,如果输入对象的属性值本身不是众所周知类型的实例(后者包括单值类型,包括 .NET 原始类型例如[int],而不是由多个属性组成的类型),它们被它们的.ToString()表示替换(例如,类型System.IO.DirectoryInfo有一个.Parent属性是另一个System.IO.DirectoryInfo实例,这意味着.Parent 属性值序列化为该实例的 .ToString() 表示形式,即其完整路径字符串);简而言之:非自定义(标量)对象序列化,使得本身不是众所周知类型实例的属性值被它们的.ToString() 表示替换;具体示例见this answer
    相比之下,explicit 通过 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

    【讨论】:

      【解决方案3】:

      对于那些感兴趣的人,下面是我实现的代码。根据$credential 是否通过,Invoke-Sqlcmd 将直接运行或使用后台作业。

      我不得不使用-As DataTables 而不是-As DataSet,因为后者似乎在序列化/反序列化方面存在问题(请参阅accepted answer 了解更多信息)。

      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
      }
      

      【讨论】:

      • 很高兴知道使用System.DataTable 会产生可用的模拟对象(您将获得表的System.Collections.ArrayList 实例,以及其System.DataRow 实例的自定义对象。这样做的原因是PowerShell 具有内置逻辑,可以将 System.DataTable 隐式视为其数据行的数组,而这不适用于 System.DataSet
      猜你喜欢
      • 2017-03-03
      • 1970-01-01
      • 1970-01-01
      • 2014-05-14
      • 1970-01-01
      • 1970-01-01
      • 2023-01-29
      • 1970-01-01
      相关资源
      最近更新 更多