Process Block - When is Foreach Needed ?

I was asked today about the Begin{} Process{} End{} blocks and why was Foreach needed inside the Process block. I have seen it with and without but to be honest I could not give a good answer to the question.

Example 1 -

Function Get-ServerStuff
    [CmdletBinding()]
    param(
        [Parameter(Position=0,
                    ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullorEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME,
 
        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
    )

 BEGIN {
        $Date = (get-date).ToString('MM-dd-yy-HHmm')
        $Opt = New-CimSessionOption -Protocol Dcom
        $CS = $null
        $SessionParams = @{
            ErrorAction = 'Stop'
        }
        If ($PSBoundParameters['Credential']) {
            $SessionParams.Credential = $Credential
        }
}

    PROCESS {
        $ServerInfoReport = @()
        ForEach ($Computer in $ComputerName){
        # Checking connection to Server If cannot connect drop this server and go to the next one.
        Try {
            $Check1 = Test-Connection $Computer -Count 1 -ErrorAction Stop | Select-Object Address,IPV4Address
            Write-Host 'Test-Connection successful for Server '  $Check1.Address  $Check1.IPV4Address -ForegroundColor Yellow
            "`r`n"
        } # End Try
        Catch [System.Net.NetworkInformation.PingException]{
            $ErrorMessage = $Computer + '  [CATCH]  ' + $Check1.IPV4Address + ' ' + $_.Exception.Message
            "`r`n"
            Write-Warning -Message $ErrorMessage
            $ErrorMessage | Out-File "C:\Temp\ServerInfo_Errors $Date.txt" -Append
            #Write-Warning -Message "[CATCH] Unable to connect to $Computer. Verify $Computer is online and try again."
            "`r`n"
            Start-Sleep -s 5
            Continue
        } # End Catch   

Example 2 - Where I am piping extension numbers from a CSV file…

Import-CSV C:\Users\tbolton\Temp\ProvRange.csv | Get-ExtensionCSV


Function Get-ExtensionCSV {
      [CmdletBinding()]
      Param(
            [Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$true)]
            [string]$Extension
      )

Begin {
	$CSU=Get-CSuser -Filter {LineURI -ne $Null} | Select DisplayName,LineURI
	$GU=Get-User -ResultSize Unlimited -Filter {Phone -ne $Null} | Select DisplayName,Phone
	$GetOTP=Get-User -ResultSize Unlimited -Filter {OtherTelePhone -ne $Null} | Select DisplayName,OtherTelePhone
	$GetOF=Get-User -ResultSize Unlimited -Filter {OtherFax -ne $Null} | Select DisplayName,OtherFax
    $GetC=Get-Contact -Filter {Phone -ne $Null} | Select DisplayName,Phone
    $GetExC=Get-CsExUmContact -Filter {LineURI -ne $Null} | Select DisplayName,DisplayNumber
	$GUM=Get-UMMailbox -ResultSize Unlimited -Filter {EmailAddresses -ne $Null} | Select DisplayName,EmailAddresses
	$GRG=Get-CsRgsWorkflow | Select Name,LineURI
	$GetA=Get-csanalogdevice -Filter {LineURI -ne $Null} | Select DisplayName,LineURI
	$GetCO=Get-CsCommonAreaPhone -Filter {LineURI -ne $Null} | Select DisplayName,LineURI
	$GetCf=Get-CsDialInConferencingAccessNumber -Filter {LineURI -ne $Null} | Select DisplayName,LineURI 
}
       
Process {
    $CSUser = $CSU | ?{$_.LineURI -like "*$Extension*"} # Lync
	$GetUser = $GU | ?{$_.Phone -like "*$Extension*"}	 # Active Directoy
	$GetOtherTelePhone = $GetOTP | ?{$_.OtherTelePhone -Like "*$Extension*"} # Active Directoy  '*$Extension*'"
	$GetOtherFax = $GetOF | ?{$_.OtherFax -Like "$Extension*"} # Active Directoy  '*$Extension*'"
    $GetContact = $GetC | ?{$_.Phone -Like "*$Extension*"}
    $GetExUmContact = $GetExC | ?{$_.LineURI -Like "*$Extension"}
	$GetUM = $GUM | ?{$_.EmailAddresses -Like "EUM:$Extension*"} # Exchange
	$GetRG = $GRG | ?{$_.LineURI -like "*$Extension"}
	$GetAnalog = $GetA | ?{$_.LineURI -like "*$Extension"} # GGet-CsAnalogDevice -Filter {LineUri -like "tel:+1425555*"}
	$GetCommon = $GetCO | ?{$_.LineURI -like "*$Extension"} # Get-CsCommonAreaPhone  -Filter {LineUri -eq "tel:+14255551234"}
	$GetConf = $GetCF | ?{$_.LineURI -like "*$Extension"} # Get-CsDialInConferencingAccessNumber -Filter {LineUri -like "tel:+1*$Extension"}

	$NotFound = $Extension + " - N/A"

# Creating new Objects
	$obj = New-Object -TypeName PSObject

# If statments - If found will show information, if not will show Extension N/A.
	If ($CSUser	-ne $null)
	{
	$obj | Add-Member -MemberType NoteProperty -Name "Lync URI" -Value ($CSUser.DisplayName)
	}	Else {
		$obj | Add-Member -MemberType NoteProperty -Name "Lync URI" -Value ($NotFound)
		}

Etc…

What is the rule as to when Foreach is needed? I was under the impression that Process {} acted like Foreach…?

Process block does act alike a foreach for each input object from the pipeline, but if the input object has a property that has multiple values, or one of the input parameters accepts multiple values such as [string[]]$ComputerName, then you need to use a foreach loop inside of your process block to handle those multiple values generally speaking.

Of course you may have an input parameter that accepts multiple values that does not needs a foreach, Maybe you just want to -join them all into a single string instead of processing the individually. It’s really up to you.

The first example, which is input via single entry or multiple entries via the console or multiple via a txt file are single Computer Names.

The Second example, which is passing a row of 4 digit extensions, if passed via a CSV file.

I grabbed these examples since they were already opened on my PC but I have seen Foreach used in several books such as Ed’s PowerShell Step By Step Page: 208

Get-IPObjectDefaultEnabledFormatNonIPOutput.ps1

Function Get-IPObject([bool]$IPEnabled = $true)
{
Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $IPEnabled"
} #end Get-IPObject
Function Format-NonIPOutput($IP)
{
Begin { "Index # Description" }
Process {
ForEach ($i in $ip)
{
Write-Host $i.Index `t $i.Description
} #end ForEach
} #end Process
} #end Format-NonIPOutPut
$ip = Get-IPObject -IPEnabled $False
Format-NonIPOutput($ip)

I “thought” the only difference was that IF the pipeline was being used Begin, Process, End would be used. If NOT then they were all ignored.

So IF I enter in a Single ComputerName it just runs it through the Foreach & IF I enter in multiple it is handled by the Process Block and not the Foreach…?

Sorry for the confusion, I just want to understand it a bit better and be able to provide a decent answer.

Here is a simple example.

Function Get-EmailAddressDomains {
    [CmdLetBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [string[]]$proxyaddresses
    )
    Begin {}
    Process {
        ForEach ($ProxyAddress in $ProxyAddresses) {
            $proxyaddress.Split('@')[1]
        }
    }
    End {}
}

Get-ADUser -Filter "samaccountname -like 'csm*'" -Properties proxyaddresses | Get-EmailAddressDomains
Get-EmailAddressDomains -proxyaddresses "test@1.2", "Test@2.1", "Test@3.3"
Get-ADUser -Filter "samaccountname -like 'abc*'" -Properties proxyaddresses | Get-EmailAddressDomains
Get-EmailAddressDomains -proxyaddresses "test@1.2", "Test@2.1", "Test@3.3"

domain1.com
domain2.com
domain3.com
domain2.com
domain1.com
1.2
2.1
3.3

Remember that the Process block is basically a foreach object for the pipeline input only. What is being input at the pipeline? An AD User Object. It has a property “proxyaddresses” that has multiple values. In order to handle that I’m going to have to use a foreach inside of the process block.

The scenario from this example:

Function Get-IPObject([bool]$IPEnabled = $true)
{
    Get-WmiObject -class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $IPEnabled"
} #end Get-IPObject

Function Format-NonIPOutput($IP)
{
    Begin { "Index # Description" }
    Process {
        ForEach ($i in $ip)
        {
            Write-Host $i.Index `t $i.Description
        } #end ForEach
    } #end Process
} #end Format-NonIPOutPut

$ip = Get-IPObject -IPEnabled $False
Format-NonIPOutput($ip)

Format-NonIPOutput is not using pipeline input, they are passing the collection in via the position 1 parameter. so it needs the foreach to process the parameter since there is no pipeline input

Ah I see it now! Thank you Curtis!

And “now” I find one of Don’s post which explains it as well…

https://technet.microsoft.com/en-us/magazine/hh413265.aspx

Thanks again Curtis!