Ash
Ash

Reputation: 3246

How do I fix the problem I have with passing all objects output via the pipeline?

I am writing a PowerShell module to make use of the API for a Storage System in C# and I have come across a problem I cannot get my head around. I have one command that can pipe one or more objects in to another and ProcessRecord() process them all individually as you would expect. However, this is only when I name the items in the first cmdlet, or save it as a variable in PowerShell first.

Here is what I see in PowerShell when I do this:

PS C:\> $a = Show-ISSFileSystem -Name fs1
PS C:\> $b = Show-ISSFileSystem -Name fs1,cesSharedRoot
PS C:\> $c = Show-ISSFileSystem    # Contains the two objects listed above.

# Types
PS C:\> $a.Gettype().Fullname
ISS.FileSystemInfo.Filesystem
PS C:\> $b.Gettype().Fullname
System.Object[]
PS C:\> $c.Gettype().Fullname
ISS.FileSystemInfo.Filesystem[]

# Introduce second command
PS C:\> $a | Show-ISSFileset                    # Returns as expected
PS C:\> $b | Show-ISSFileset                    # Returns as expected
PS C:\> $c | Show-ISSFileset                    # Returns as expected
PS C:\> Show-ISSFileSystem | Show-ISSFileSet    # Fails, complaining about the input object - Custom Class(Filesystem)

Show-ISSFileset : The input object cannot be bound to any parameters for the command either because the command does
not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

# Counting objects
PS C:\> (Show-ISSFileSystem -Name fs1 | Measure-Object).Count               # Returns 1 as expected
PS C:\> (Show-ISSFileSystem -Name fs1,cesSharedRoot | Measure-Object).Count # Returns 2 as expected
PS C:\> (Show-ISSFileSystem | Measure-Object).Count                         # Returns 1, even though the variable has two objects
PS C:\> ($c | Measure-Object).Count                                         # Saved to variable first, the command above correctly returns 2 as expected.

I can add parts of code if necessary, but I just wanted to know if anyone had any quick thoughts about what might be happening here. It can quite clearly deal with multiple objects, but not directly from the first function unless we name one (I have used ValueFromPipeline and not ValueFromPipelineByProperty). There is a lot of code and I was struggling to provide a minimal example.

It is like the first command is mashing the objects together when run directly and the second one doesn't know what the input is. Anyone had a similar problem?

UPDATE:

Okay, so I realised that it is something strange going on with the output. After I converted the response from JSON, if the Name parameter was specified, I was running it through .Where() and the end result was a List of objects. Anything going through that was coming out and through the pipeline okay. If Name wasn't specified, it was writing the converted Json object directly.

Didn't work:

// Convert from Json
FileSystemInfo _convertJson = FileSystemInfo.FromJson(_response);

// Filter by name if requested
if (Name != null)
{
    List<Filesystem> FileSystems = _convertJson.Filesystems.Where(
        f =>
            Regex.IsMatch(f.Name.ToString(),
                string.Format("(?:{0})", string.Join("|", Name)))).ToList();
    FileSystems.ForEach(WriteObject);
}
else
{
    WriteObject(_convertJson.Filesystems);
}

Works:

// Convert from Json
FileSystemInfo _convertJson = FileSystemInfo.FromJson(_response);

// Filter by name if requested
if (Name != null)
{
    List<Filesystem> FileSystems = _convertJson.Filesystems.Where(
        f =>
            Regex.IsMatch(f.Name.ToString(),
                string.Format("(?:{0})", string.Join("|", Name)))).ToList();
    FileSystems.ForEach(WriteObject);
}
else
{
    _convertJson.Filesystems.ToList().ForEach(WriteObject);
}

Perhaps this isn't the best answer and someone can correct me. I have only been learning C# three months.

UPDATE2:

Many thanks to PetAlSer and Mathias below for pointing me in the right direction. The list conversion above was not necessary, only needed to enumerate the collection as part of WriteObject so that the second cmdlet would receive each object.

WriteObject(_convertJson.Filesystems, true);

Upvotes: 0

Views: 213

Answers (1)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174445

As PetSerAl already pointed out in the comments:

It is core PowerShell behavior: when first (or only) element of pipeline is an expression and that expression result in collection, then that collection is enumerated and its content passed by pipeline instead of collection itself.

Cmdlet.WriteObject() has an overload that allows you to instruct the pipeline processor to not unroll enumerable output which you can use with List<T>.ForEach() (assuming T is enumerable) with a bit of currying:

Action<object> WriteObjectWithoutEnumeration = 
    o => this.WriteObject(o, false);

FileSystems.ForEach(WriteObjectWithoutEnumeration);

// or, without ForEach:
WriteObject(_convertJson.Filesystems, false);

Upvotes: 1

Related Questions