I am trying to write a simple PowerShell cmdlet in C# that accepts an instance of ArrayList and adds an item to it. The issue I've encountered is that a copy of the array list is passed to cmdlet, not the actual object as I would expect. Is there a way to pass parameter by reference? The following code outputs just "Count is 0" for ArrayList $B.
Note: to run the program in PowerShell 7.1 you need to target .NET 5 and install Microsoft.PowerShell.SDK and System.Management.Automation Nuget packages.
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. It appears this behavior is caused by New-Object cmdlet. If I replace it with [System.Collections.ArrayList] @() then I get what I need. Still I don't understand why it works this way.