Piping an array into a function has Foreach and Foreach-Object only dealing with

Welcome Forums General PowerShell Q&A Piping an array into a function has Foreach and Foreach-Object only dealing with

Viewing 6 reply threads
  • Author
    Posts
    • #210549
      Participant
      Topics: 9
      Replies: 29
      Points: 191
      Rank: Participant

      I’m piping an array of PSCustomObjects into a function but when I try to iterate over the array, the function only deals with the last item of the array.

      Tested both foreach and ForEach-Object but the results are the same.

      Function Get-MailInfo {
      	$MailInfoArray = @()
      
      	$ReturnArray | ForEach-Object {
      		$User = Get-ADUser -Server $server -Identity $_.UserName -Properties Manager
      		if ((Get-ADUser -Server $server -Identity $_.UserName -Properties Manager).Manager -ne $null) {
      			$Manager = Get-ADUser -Server $server -Identity $User.Manager -ErrorAction Stop
      		}
      		else {
      			$Manager = Get-ADUser -Server $server -Identity $_.UserName
      		}
      		$PSObject = [PSCustomObject]@{
      			User = $_.UserName
      			GivenName = $User.GivenName
      			SurName = $User.Surname
      			To = ($User.UserPrincipalName).ToLower()
      			Cc = ($Manager.UserPrincipalName).ToLower()
      			Computer = $_.ComputerName
      		} 
      		$MailInfoArray += $PSObject
      	}
      	return $MailInfoArray
      }
      
      $MailInfoArray
      $MailInfoArray.Length
      
      function Send-MailInfo {
          [CmdletBinding()]
          param(
              [Parameter(ValueFromPipeline = $true)]
              [array]$MailInfoArray
          )
      	# I've inc
          foreach ($obj in $MailInfoArray) {
              Add-Content -Path ".\$($obj.User)-fe.txt" -Value "$($obj.GivenName)" -Encoding UTF8
          }
      	
      	$MailInfoArray | ForEach-Object {
      		Add-Content -Path ".\$($_.User)-fo.txt" -Value "$($_.GivenName)" -Encoding UTF8
      	}
      }
      
      $MailInfoArray | Send-MailInfo

      If I call the the last function like this however Send-MailInfo (Get-MailInfo) it works as intended. Does the pipeline not support arrays naturally or is there something else I should be adding in the parameter block?

    • #210561
      Participant
      Topics: 12
      Replies: 523
      Points: 1,214
      Helping Hand
      Rank: Community Hero

      Change line 32 from

          [array]$MailInfoArray
      

      to

          [PSCustomObject[]]$MailInfoArray
      
    • #210579
      Participant
      Topics: 9
      Replies: 29
      Points: 191
      Rank: Participant

      Instead of telling the function simply that it gets an array without specifying an array of what, you tell it that it gets a number of PSCustomObjects in an array?
      Makes sense, however it seems that I’m still missing something somewhere.

      This is another example, where I’ve changed to from [array] to [PSCustomObject[]] but piping it still only returns the last object in the array.

      function Get-ComputerUserArray {
        [CmdletBinding()]
        param (
          [parameter()]
          [string]$Path
        )
        $reg = '(?<cname>[A-Z]{2}\d{4}).* ADM\\(?<uname>[a-z]{2}\d{4})'
      
        $ReturnArray = Switch -Regex -File $Path {
          { $_ -cmatch $reg } {
            $_ -cmatch $reg | Out-Null
            [PSCustomObject]@{
              ComputerName  = $Matches['cname']
              UserName      = $Matches['uname']
            }
          }
        }
        $ReturnArray.Length
        return $ReturnArray
      }
      
      function Show-ReturnArray {
        [CmdletBinding]
        param (
          [Parameter(ValueFromPipeLine = $true)]
          [PSCustomObject[]]$ReturnArray
        )
        $ReturnArray.Length
        $ReturnArray | ForEach-Object {
          $_.ComputerName
          $_.UserName
        }
      }
      
      # This does not work - only returns the last item in the array!
      Get-ComputerUserArray -Path ".\TestFile.txt" | Show-ReturnArray
      
      # This works - returns all the items in the array!
      Show-ReturnArray -ReturnArray (Get-ComputerUserArray -Path ".\TestFile.txt")
    • #210675
      Participant
      Topics: 6
      Replies: 667
      Points: 97
      Helping Hand
      Rank: Member

      It seems like you are missing your processing structure for pipeline input.  I don’t see your begin, process, and end blocks.

      IE

      #Sample Objects for piping
      $customObjects = 0..5 |
      ForEach-Object {
          $props = @{
              Name = "Object$_"
              Path = "C:\User\Object$_"
          }
          New-Object -TypeName PSObject -Property $props
      }
      
      #Sample function for processing pipeline input
      Function Test-Piping {
          [CmdletBinding()]
          Param (
              [Parameter(ValueFromPipeline)]$data
          )
      
          Begin {
              Write-Host "Beginning pipe to function" -ForegroundColor Green
          }
      
          Process {
              Write-Host "Processing for $($data.Name) on $($data.path)" -ForegroundColor Cyan
          }
      
          End {
              Write-Host "Ending pipe to function" -ForegroundColor Red
          }
      
      }
      
      #Sample piping into Sample function
      $customObjects | Test-Piping

      Results:

      Beginning pipe to function
      Processing for Object0 on C:\User\Object0
      Processing for Object1 on C:\User\Object1
      Processing for Object2 on C:\User\Object2
      Processing for Object3 on C:\User\Object3
      Processing for Object4 on C:\User\Object4
      Processing for Object5 on C:\User\Object5
      Ending pipe to function
    • #211941
      Participant
      Topics: 9
      Replies: 29
      Points: 191
      Rank: Participant

      Thank you for your input, and I’m sorry it took me so long to reply.
      Been busy prepping my home office for a week of working from home.

      I’m getting closer, but I’m still missing something about the processing of the array.

      I set up the function with begin/process/end blocks and mostly the processing of the objects in the piped in array seems to work as it should.
      However I don’t understand why the array reports a lenght of 0 or 1 objects rather than the full lenght of the array.

      My intention is to ask the user to confirm that they want to send the mail to the number of objects in the array, but if I place $MailInfoArray.Length in the begin block it reports 0 objects.
      If on the other hand I place it in the process-block it reports 1 object, and it seems to iterate over the array though $MailInfoArray.Length is placed outside of the ForEach-Object loop.

      Truncated input:

      User      : un1234
      GivenName : User1
      SurName   : Name
      To        : user1.name@email.address
      Cc        : manager.name@email.address
      Computer  : CN1234
      [...]
      

      Output:

      1
      user1.name@email.address
      manager.name@email.address
      Computer: CN1234
      1
      user2.name@email.address
      manager.name@email.address
      Utbyte av dator: CN2345
      Mail sent!

      Function:

      function Send-ReturnMail {
        [CmdletBinding()]
        param (
          [Parameter(ValueFromPipeLine = $true)]
          [PSCustomObject[]]$MailInfoArray
        )
        begin {
          $credential =  Get-Credential 
        }
      
        process {
          # $Send = Read-Host -Prompt "You will be mailing $($MailInfoArray.Length) users. rnGo ahead? [Y/n] "
      
          $MailSubject = "Computer: $($_.Computer)"
          $MailBody = @"
          
        
        
      	[...]
        
      
      "@
      
      	$mailParams = @{
            Credential  = $credential
            SmtpServer  = 'smtp.mail.address'
            Port        = '587'
            UseSSL      = $true
            Encoding    = 'UTF8'
            From        = 'itsupport <it.support@email.address>'
            BodyAsHtml  = $true
            Body        = $MailBody
            Subject     = $MailSubject # $($_.Computer)  
            To          = $($_.To)
            Cc          = $($_.Cc)
            # ReplyTo     = 'itsupport@email.address'
          }
      
          $MailInfoArray.Length
          # if ((($Send).ToLower() -eq 'y') -or (($Send).ToLower() -eq 'yes') -or (($Send).ToLower() -eq '')) {
            $MailInfoArray | ForEach-Object {
              # Send-MailMessage @mailParams -WhatIf
              $mailParams.To
              $mailParams.Cc
              $mailParams.Subject
              #$mailParams.Body
            }
          #}
        }
      
        end {
          "Mail sent!"
        }
      }
    • #212019
      Participant
      Topics: 4
      Replies: 82
      Points: 251
      Helping Hand
      Rank: Contributor

      When an array of objects is passed over the pipeline to a function like yours, each element in the array is processed one at a time like a loop, so when calling your function over the pipeline, the process block $MailInfoArray is a single element each iteration and not the entire array.  Try calling your function without using the pipeline i.e.

      Send-ReturnMail -MailInfoArray <array argument>

      Do you see a difference?  Here’s a simplified example:

      function test-piping {
      [cmdletbinding()]
      Param(
      [parameter(ValueFromPipeline)][string[]]$array
      )
      $array.count
      }
      
      (1..10) | test-piping 
      test-piping -array (1..10)
      
      
      1
      10
    • #212364
      Participant
      Topics: 9
      Replies: 29
      Points: 191
      Rank: Participant

      Thank you.

      While testing I had a previous version of the calling script invoking this script with “regular” parameter which worked as intended and I didn’t understand why, when the pipeline didn’t. But that of course explains it.

      When I think about it I know that the pipe sends discrete items when doing for instance Get-ChildItem *.txt | Get-Content, I just imagined that it would send an array as a single unit rather than expand it.

Viewing 6 reply threads
  • You must be logged in to reply to this topic.