Author Posts

September 15, 2015 at 7:51 am

Hello, I am creating a module that wraps a REST API and having an issue with pipeline support.

I have a get function like so...

function Get-Volume{
[CmdletBinding(DefaultParameterSetName='AllVolumes')]
param ( [Parameter(Mandatory=$true, 
                    ValueFromPipeline=$true,
                    Position=0,
                    ParameterSetName='VolByName')]
        [Alias('n')] 
        [string]$Name,
        [Parameter(Mandatory=$true,
                    ParameterSetName='VolByIndex')]
        [Alias('i')] 
        [string]$ID
)
    Begin{
        $UriObject = 'volumes'
    }
    Process{
        # Return details of volume names passed by parameter or pipeline
        if($Name){
            $UriString = ($UriObject + '/?name=' + $Name)
            Invoke-RestMethod -Method Get -Uri ($Global:APIBaseUri + $UriString) -Headers $Global:APIHeaders).content
        }
    }
    End{
        # Return detail of specific volume by ID
        if($ID){
            $UriString = ($UriObject + '/' + $ID)
        }
        # No parameters passed return details of all volumes
        if($PSCmdlet.ParameterSetName -eq 'AllVolumes'){
            $UriString = ($UriObject + '/')
            (Get-StorageItem -UriString $UriObject).$UriObject | ForEach-Object{(Invoke-RestMethod -Method Get -Uri ($Global:APIBaseUri + $UriString + '?name=' + ($_.Name)) -Headers $Global:APIHeaders).Content}    
        }
    }
} # Get-Volume

And a set finction like so...

function Set-Volume{
[CmdletBinding()]
param ( [Parameter(Mandatory=$true,ParameterSetName='VolUpdateByName',
                    ValueFromPipeline=$true, 
                    ValueFromPipelineByPropertyName=$true)]
        [Parameter(ParameterSetName='VolNameUpdate')]
        [Parameter(ParameterSetName='VolSizeUpdate')]
        [string]$Name,
        [Parameter(Mandatory=$true,ParameterSetName='VolUpdateByIndex')]
        [Parameter(ParameterSetName='VolNameUpdate')]
        [Parameter(ParameterSetName='VolSizeUpdate')]
        [string]$ID,
        [Parameter(Mandatory=$false,ParameterSetName='VolNameUpdate')]
        [ValidateNotNullOrEmpty()]
        [string]$NewVolName,
        [Parameter(Mandatory=$false,ParameterSetName='VolSizeUpdate')]
        [ValidateNotNullOrEmpty()]
        [string]$NewVolSize
)
    Begin{
        $UriObject = 'volumes'
    }
    Process{
        if($Name){
            $UriString = ($UriObject + '/?name=' + $Name)
            $JSoNBody = New-Object -TypeName psobject

            # Optional Parameters
            if($NewVolName){$JSoNBody | Add-Member -MemberType NoteProperty -Name vol-name -Value $NewVolName}
            if($NewVolSize){$JSoNBody | Add-Member -MemberType NoteProperty -Name vol-size -Value $NewVolSize}
            Invoke-RestMethod -Method Put -Uri ($Global:APIBaseUri + $UriString) -Headers $Global:APIHeaders -Body ($JSoNBody | ConvertTo-Json)
        }
    }     
    End{
        if($ID){
            $UriString = ($UriObject + '/' + $ID)
            $JSoNBody = New-Object -TypeName psobject

            # Optional Parameters
            if($NewVolName){$JSoNBody | Add-Member -MemberType NoteProperty -Name vol-name -Value $NewVolName}
            if($NewVolSize){$JSoNBody | Add-Member -MemberType NoteProperty -Name vol-size -Value $NewVolSize}
            Invoke-RestMethod -Method Put -Uri ($Global:APIBaseUri + $UriString) -Headers $Global:APIHeaders -Body ($JSoNBody | ConvertTo-Json)
        }
    }
} # Set-Volume

When I do a get the following object is returned.

vol-id                   : {f4babab7df3d426cb56fc0fdf89a1ef7, DAM01, 64}
obj-severity             : information
guid                     : f4babab7df3d426cb56fc0fdf89a1ef7
index                    : 64
naa-name                 : 
creation-time            : 2015-09-15 01:06:41
vol-size                 : 10485760
name                     : DAM01

I can use a the pipeline on both of the functions in the following manner

@('DAM01','DAM02') | Get-Volume
@('DAM01','DAM02') | Set-Volume -NewVolName 'DAM03'

When I try to use the functions together like so...

Get-Volume -Name 'DAM01' | Set-Volume -NewVolName 'DAM03'

The set function does not seem to be able to pick the name property from the output of the get.
It is trying to use the entire object as parameter input.
Here is a snippet of output from a trace of the command above.

ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Set-XIOVolume]
ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Out-Default]
ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.Management.Automation.ErrorRecord]
ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
ParameterBinding Information: 0 :     Parameter [InputObject] PIPELINE INPUT ValueFromPipeline NO COERCION
ParameterBinding Information: 0 :     BIND arg [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.] to parameter [InputObject]
ParameterBinding Information: 0 :         BIND arg [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.] to param [InputObject] SUCCESSFUL
ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Out-Default]

What am I missing here and any suggestions on how to correct it so the set can properly identify the name property for parameter input?

Thanks,

Dave Muegge

September 15, 2015 at 9:02 am

ValueByPropertyName is expecting a PSObject, so what is this returning:

(Get-StorageItem -UriString $UriObject).$UriObject | ForEach-Object{(Invoke-RestMethod -Method Get -Uri ($Global:APIBaseUri + $UriString + '?name=' + ($_.Name)) -Headers $Global:APIHeaders).Content}    

Here is an example passing the values through the pipeline:

function Get-Volume {
    [CmdletBinding(DefaultParameterSetName = 'AllVolumes')]
    param (
        [Parameter(Mandatory = $true,
                   ValueFromPipeline = $true,
                   Position = 0,
                   ParameterSetName = 'VolByName')]
        [Alias('n')]
        [string]$Name
    )
    begin { }
    process {
        New-Object -TypeName PSObject -Property @{
            Name = $Name;
        }
    }
    end { }
}

function Set-Volume {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'VolUpdateByName',
                   ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = 'VolNameUpdate')]
        [Parameter(ParameterSetName = 'VolSizeUpdate')]
        [string]$Name
    )
    begin { }
    process { Write-Verbose ("Processing {0}..." -f $Name) }
    end { }
}

@('DAM01', 'DAM02') | Get-Volume | Set-Volume -Verbose

Also, I would recommend naming these that is not already an existing cmdlet. If you worked for Tech Corp, maybe Get-TCVolume or something like that. If that script runs and your function Get-Volume is in memory and you wanted to call the cmdlet Get-Volume it be confusing.

September 15, 2015 at 12:20 pm

Rob, Thanks for the reply

Thanks for the advice about the function naming and I actually do have prefixes, which I removed to make the post more generic.

The following line does return a PSCustomObject which by my understanding is the same as a PSObject just a newer version.

Invoke-RestMethod -Method Put -Uri ($Global:APIBaseUri + $UriString) -Headers $Global:APIHeaders -Body ($JSoNBody | ConvertTo-Json)

Here is an example of the type output of the function.

(Get-XIOVolume -Name 'DAM01').GetType()
IsPublic IsSerial Name                                     BaseType                                                                                                                                                                                                                                                                                   
-------- -------- ----                                     --------                                                                                                                                                                                                                                                                                   
True     False    PSCustomObject                           System.Object

I actuall did try capturing the output and splatting to an object like so

$ReturnObj = Invoke-RestMethod -Method Put -Uri ($Global:APIBaseUri + $UriString) -Headers $Global:APIHeaders -Body ($JSoNBody | ConvertTo-Json)
New-Object -TypeName psobject -Property @ReturnObj

This caused a conversion error and did not solve the issue.

Any other ideas or suggestions are appreciated.

Dave

October 2, 2015 at 12:13 pm

I thought I would update this as I found my issue. The pipeline problem was actually a side affect of having a poor design of Parameter Sets.

Tip if you are using a fairly complex setup of multiple parameter sets and having trouble with getting input from the pipeline. The issue just may be the parameter set design.

Thanks,

Dave