【问题标题】:Pass parameter by reference to cmdlet in PowerShell通过引用 PowerShell 中的 cmdlet 传递参数
【发布时间】:2021-03-22 02:23:55
【问题描述】:

我正在尝试用 C# 编写一个简单的 PowerShell cmdlet,它接受一个 ArrayList 实例并向其中添加一个项目。我遇到的问题是数组列表的副本被传递给 cmdlet,而不是我期望的实际对象。有没有办法通过引用传递参数?以下代码仅输出 ArrayList $B 的“Count is 0”。

注意:要在 PowerShell 7.1 中运行程序,您需要以 .NET 5 为目标并安装 Microsoft.PowerShell.SDK 和 System.Management.Automation Nuget 包。


using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

namespace CustomPowerShellCmdlet
{
    class Program
    {
        static void Main(string[] args)
        {
            string powerShellScript = @"

$B  = New-Object System.Collections.ArrayList
Add-CollectionItem -Collection $B -Item '1'
Add-CollectionItem -Collection $B -Item '2'

Write-Output  ('Count is ' + $B.Count)
            ";


            var output = RunPowerShellScript(powerShellScript);

            foreach (PSObject psObject in output)
            {
                Console.WriteLine(psObject.BaseObject.ToString());
            }
        }

        private static Collection<PSObject> RunPowerShellScript(string powerShellScript)
        {
            InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
            initialSessionState.Commands.Add(new SessionStateCmdletEntry("Add-CollectionItem", typeof(AddCollectionItemCmdlet), null));

            using (var runspace = RunspaceFactory.CreateRunspace(initialSessionState))
            {
                runspace.Open();

                using (var ps = PowerShell.Create())
                {
                    ps.Streams.Error.DataAdding += Output_DataAdding;
                    ps.Streams.Debug.DataAdding += Output_DataAdding;
                    ps.Runspace = runspace;

                    ps.AddScript(powerShellScript);

                    return ps.Invoke();
                }
            }
        }

        private static void Output_DataAdding(object sender, DataAddingEventArgs e)
        {
            Console.WriteLine(e.ItemAdded.ToString());
        }

        [Cmdlet(VerbsCommon.Add, "CollectionItem")]
        public class AddCollectionItemCmdlet : Cmdlet
        {
            [Parameter(Mandatory = true)]
            [AllowEmptyCollection]
            public ArrayList Collection { get; set; }

            [Parameter(Mandatory = true)]
            public object Item { get; set; }

            /// <summary>
            /// This static field is needed for demo purposes only.
            /// </summary>
            public static List<object> AddedItems = new List<object>();

            protected override void BeginProcessing()
            {
                Console.WriteLine("A new collection is passed to the cmdlet: {0}", !AddedItems.Any(x => ReferenceEquals(x, Collection)));
                AddedItems.Add(Collection);

                Collection.Add(Item);
            }
        }
    }
}

UPD1. 看来此行为是由 New-Object cmdlet 引起的。如果我用 [System.Collections.ArrayList] @() 替换它,那么我得到了我需要的东西。我仍然不明白为什么它会这样工作。

【问题讨论】:

  • 什么版本的 PowerShell?我无法在 PowerShell 5.1、7.0.3 和 7.1.0 中重现此行为。也许您正在使用加载了先前版本的模块的会话中进行测试?
  • @MathiasR.Jessen 我可以在 5.1 和 7.1 中重现它。我更新了问题中的代码以阐明如何使用 cmdlet。它在运行空间中注册为命令,我没有在模块中使用它。

标签: c# powershell cmdlets


【解决方案1】:

对象被传递给 cmdlet,但它没有被返回,即 set 被调用,但 get 未被调用。您需要通过管道返回集合并将其重新分配给原始变量。这是工作代码:

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

namespace CustomPowerShellCmdlet
{
    class Program
    {
        static void Main(string[] args)
        {
            string powerShellScript = @"
Write-Output ('PowerShell Version is ' + $PSVersionTable.PSVersion)

$A  = New-Object System.Collections.ArrayList
$A.Add('3') | Out-Null
Write-Output  ('Count is ' + $A.Count)

$B  = New-Object System.Collections.ArrayList
$B = (Add-CollectionItem -Collection $B -Item '1')
$B = (Add-CollectionItem -Collection $B -Item '2')
foreach ($Item in $B) {
    Write-Output $Item
}
Write-Output  ('Count is ' + $B.Count)
";
            var output = RunPowerShellScript(powerShellScript);

            foreach (PSObject psObject in output)
            {
                Console.WriteLine(psObject.BaseObject.ToString());
            }
        }

        private static Collection<PSObject> RunPowerShellScript(string powerShellScript)
        {
            InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
            initialSessionState.Commands.Add(new SessionStateCmdletEntry("Add-CollectionItem", typeof(AddCollectionItemCmdlet), null));

            using (var runspace = RunspaceFactory.CreateRunspace(initialSessionState))
            {
                runspace.Open();
                using (var ps = PowerShell.Create())
                {
                    ps.Runspace = runspace;
                    ps.AddScript(powerShellScript);
                    return ps.Invoke();
                }
            }
        }

        [Cmdlet(VerbsCommon.Add, "CollectionItem")]
        public class AddCollectionItemCmdlet : Cmdlet
        {
            [Parameter(Mandatory = true)]
            [AllowEmptyCollection]
            public ArrayList Collection
            {
                get; set;
            }

            [Parameter(Mandatory = true)]
            public object Item { get; set; }

            protected override void ProcessRecord()
            {
                Collection.Add(Item);
                WriteObject(Collection);
            }
        }
    }
}

【讨论】:

  • 我也试过了,在视觉上它似乎可以满足我的需求,但在幕后它仍然是集合的副本。我稍微修改了我的代码以包含调试信息。无论采用哪种方法,它都会输出'一个新集合被传递给 cmdlet:True 一个新集合被传递给 cmdlet:True'
【解决方案2】:

这似乎是 PowerShell 中的一个错误,请参阅 https://github.com/PowerShell/PowerShell/issues/14394

【讨论】:

    猜你喜欢
    • 2018-04-14
    • 1970-01-01
    • 2016-12-18
    • 2018-02-17
    • 2010-10-07
    • 1970-01-01
    • 2011-08-21
    • 2010-09-14
    • 2017-09-25
    相关资源
    最近更新 更多