Process Block - When is Foreach Needed ?

This topic contains 5 replies, has 2 voices, and was last updated by Profile photo of Tim Bolton Tim Bolton 1 year, 2 months ago.

  • Author
    Posts
  • #31859
    Profile photo of Tim Bolton
    Tim Bolton
    Participant

    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..?

  • #31861
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    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.

  • #31862
    Profile photo of Tim Bolton
    Tim Bolton
    Participant

    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.

  • #31863
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    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

  • #31864
    Profile photo of Tim Bolton
    Tim Bolton
    Participant

    Ah I see it now! Thank you Curtis!

  • #31865
    Profile photo of Tim Bolton
    Tim Bolton
    Participant

    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!

You must be logged in to reply to this topic.