locked
PowerShell Begin/Process/End question RRS feed

  • Question

  • Hi, I am trying to get my functions and scripts written properly and I am having an issue with Begin/Process/End. As far as I can tell it does not perform as expected. 

    Take the following script for example:

    Function Write-ProperOutput
    {
    param
    (
    [Parameter(ValueFromPipeline=$True)]
    [string[]]$inputvalue
    )
    
    Begin {write-verbose "I am starting"}
    
    Process
    {
    write-verbose "this value is $inputvalue"
    $inputvalue
    }
    
    End {write-verbose "I am done now"}
    }

    Here is the output I get when I run this function with pipeline vs as a regular parameter

    PS C:\Windows\system32> 1, 2, 3, 4 | Write-ProperOutput -Verbose
    VERBOSE: I am starting
    VERBOSE: this value is 1
    1
    VERBOSE: this value is 2
    2
    VERBOSE: this value is 3
    3
    VERBOSE: this value is 4
    4
    VERBOSE: I am done now
    PS C:\Windows\system32> Write-ProperOutput -Verbose 1, 2, 3, 4
    VERBOSE: I am starting
    VERBOSE: this value is 1 2 3 4
    1
    2
    3
    4
    VERBOSE: I am done now

    The question I have is, how do I make it so in both cmd scenarios the output is the same? I mean I know how to code my way out of this issue but shouldn't this work by default? What logic am I missing? 

    Thanks in advance!!!


    Amit B. Agarwal

    Sunday, December 4, 2016 12:19 PM

Answers

All replies

  • Process {
        Foreach ($Value in $inputvalue){
            write-verbose "this value is $Value"
            $Value
        }
    }
    

    Sunday, December 4, 2016 12:34 PM
  • Thank you for the reply! But does that not remove the whole point of the begin/process/end format? I mean I could accomplish the same without begin/process/end. 

    Amit B. Agarwal

    Sunday, December 4, 2016 12:37 PM
  • How would you accomplish the same without begin/process/end?

    Link:

    https://msdn.microsoft.com/powershell/reference/5.1/Microsoft.PowerShell.Core/about/about_Functions_Advanced_Methods

    Process This block is used to provide record-by-record processing for the function. This block might be used any number of times, or not at all, depending on the input to the function. For example, if the function is the first command in the pipeline, the Process block will be used one time. If the function is not the first command in the pipeline, the Process block is used one time for every input that the function receives from the pipeline. If there is no pipeline input, the Process block is not used.

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

    Sunday, December 4, 2016 12:49 PM
  • Here is a vague example:

    Function Write-ProperOutput
    {
    param
    (
    [Parameter(ValueFromPipeline=$True)]
    [string[]]$inputvalue
    )
    
    "I am starting"
    
    If ($input)
    {
        $input | % {
            write-verbose "this value is $_"
            $_
        }
    }
    ElseIf ($inputvalue)
    {
        $inputvalue | % {
            Write-Verbose "this value is $_"
            $_
        }
    }
    
    write-verbose "I am done now"
    }

    Now in this particular case I get that what I have presented is more code than the begin/process/end example so I can see why the latter would be beneficial. and of course i could have done a function within a function to lessen the code. But I am writing a very complex script and in the end the former would probably serve me better in terms of processing. 

    but of course I could be completely wrong. I am trying to learn :-)

    Thanks again!


    Amit B. Agarwal


    EDIT: I forgot to put the write-verbose in front of I am starting. Oops. 
    • Edited by ABAgarwal Sunday, December 4, 2016 1:03 PM
    Sunday, December 4, 2016 12:59 PM
  • Depends on what your code is for. Use $Input or Process Block.

    You are using Foreach/If/Elseif in your last code, why not just use Foreach in Process Block. (Less Code)

    Read this article

    https://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/


    Sunday, December 4, 2016 1:10 PM
  • Awesome! thanks. I will be honest I only glanced at it thus far because it is probably about 6 hours past my bedtime but I think you answered my question. Much appreciated!!! 

    Amit B. Agarwal

    Sunday, December 4, 2016 1:20 PM
  • Regarding this question:

    "You are using Foreach/If/Elseif in your last code, why not just use Foreach in Process Block. "

    Its a complicated answer. IN a nutshell my script says this:

    function Start-ADUserSomething
    {
    
    Param(
    [string[]]$aduser
    )
    
    Begin{
    If (!aduser)
    {
    get-aduser -LDAPFiler (samaccountname=*)
    }
    
    Process
    {
    $array = @()
    ForEach ($user in $aduser)
    {
    $array += Do stuff
    }
    }
    
    End
    {
    Here are the results of "Do Stuff"
    }
    }

    As you can see, the function is asking for a $aduser that is not mandatory. I want the code to say if it is not specified, get everyone. If it is, then process for each with a begin/process/end. If all are specified then load the beginning code, process for each in the process block, then end. 

    Does that make sense?


    Amit B. Agarwal

    Sunday, December 4, 2016 1:32 PM
  • This is incorrect - If (!aduser)

    It should be - If (!($aduser))

    Sorry can't tell without knowing what do stuff. Maybe someone else from this forum will.

    You can use Process Block to display the result. I will use End block if i want to do something to the result.

    Ex: If i want the result to be sorted by name property

    End {$array | Sort Name}


    Sunday, December 4, 2016 3:06 PM
  • Regarding this question:

    "You are using Foreach/If/Elseif in your last code, why not just use Foreach in Process Block. "

    Its a complicated answer. IN a nutshell my script says this:

    function Start-ADUserSomething
    {
    
    Param(
    [string[]]$aduser
    )
    
    Begin{
    If (!aduser)
    {
    get-aduser -LDAPFiler (samaccountname=*)
    }
    
    Process
    {
    $array = @()
    ForEach ($user in $aduser)
    {
    $array += Do stuff
    }
    }
    
    End
    {
    Here are the results of "Do Stuff"
    }
    }

    As you can see, the function is asking for a $aduser that is not mandatory. I want the code to say if it is not specified, get everyone. If it is, then process for each with a begin/process/end. If all are specified then load the beginning code, process for each in the process block, then end. 

    Does that make sense?


    Amit B. Agarwal

    This is a completely different issue from you original issue and you script does not produce any understandable result compared to your question.  This is usually due to a lack of understanding of the technology leading to a vague or ambiguous question.

    It sounds like you may be asking how can you build a function that takes pipeline input and can display all users when the input is missing.

    The following may help you to understand how Advanced Functions and the pipeline are intended to work.

    function Start-ADUserSomething {
    	Param (
    		[Parameter(ValueFromPipeline=$true)]
    		[string[]]$SamAccountName
    	)
    	Begin {
    		If (!$SamAccountName) {
    			Write-Verbose 'No input - getting names'
    			$SamAccountName = get-aduser -Filter * | select -expand SamAccountName
    		}
    		$array=@()
        }
    	Process {
    		ForEach ($samname in $SamAccountName) {
    			if($user = Get-AdUser -LdapFilter "(SamAccountName=$samname)"){
    				Write-Verbose ('Processing ' + $user.SamAccountName)
    				$array += 'Do stuff with ' + $user.Name
                }else{
    				Write-Host "User $samname not found!" -fore Blue -back White
                }
    		}
    	}
    		
    	End {
    		Write-Verbose 'Here are the results of "Do Stuff"'
    		$array
    	}
    }
    Start-ADUserSomething -Verbose
    Start-ADUserSomething guest, nouser, $env:username -Verbose
    'guest', 'nouser', $env:username | Start-ADUserSomething -Verbose
    
    


    \_(ツ)_/

    Sunday, December 4, 2016 4:55 PM
  • However we would not likely accumulate results in a pipelineable function as this will stall or block the pipeline.  This is how we would use a pipeline.

    function Start-ADUserSomething {
    	Param (
    		[Parameter(ValueFromPipeline=$true)]
    		[string[]]$SamAccountName
    	)
    	Begin {
    		If (!$SamAccountName) {
    			Write-Verbose 'No input - getting names'
    			$SamAccountName = get-aduser -Filter * | select -expand SamAccountName
    		}
        }
    	Process {
    		ForEach ($samname in $SamAccountName) {
    			if($user = Get-AdUser -LdapFilter "(SamAccountName=$samname)"){
    				Write-Verbose ('Processing ' + $user.SamAccountName)
    				'Do stuff with ' + $user.Name
                }else{
    				Write-Host "User $samname not found!" -fore Blue -back White
                }
    		}
    	}
    		
    	End {
    		Write-Verbose 'Processing complete'
    	}
    }
    $results = Start-ADUserSomething -Verbose
    $results
    Start-ADUserSomething | Format-Table


    \_(ツ)_/

    Sunday, December 4, 2016 4:58 PM
  • Yes sorry I forgot the '$'. I was typing as I went along not copying pasting. Thanks for all your help!

    Amit B. Agarwal

    Sunday, December 4, 2016 9:49 PM
  • HI. Thanks for your answer! Sorry for the vagueness and possible incorrectness of my script. My real one is huge and I was trying to cut it down for here. 

    I see what you are saying in regards to input vs no input in your script example. But in your example it looks like you are essentially doing Get-ADUser twice for each user if there was no pipeline input or parameter input. 

    Is there a way to avoid that such as this? This is edited from the PROCESS you provided me. essentially if $samname is just a string and not an ADUser obj it would have no samaccountname property

    Process
    {
    
    ForEach ($samname in $SamAccountName)
    
    If (!$($samname.samaccountname))
    {
    $user = Get-ADUSer -LDAPFilter "(SamAccountName=$samname)"
    }
    Else
    {
    $user = $samname
    }
    
    $array += $user
    
    }


    Amit B. Agarwal

    Sunday, December 4, 2016 10:05 PM
  • I was just following your requirement which I think I noted was a bit odd.  I was also giving you a hint as to how to use "Begin"

    In reality we would likely never need or use this approach.

    It would be more like this:

    function Start-ADUserSomething {
    	Param (
    		[Alias('Name')]
    		[Parameter(
    			ValueFromPipelineByPropertyName = $true,
    			ValueFromPipeline = $true,
    			Mandatory=$true
    		)]
    		[string[]]$SamAccountName
    	)
    	Process {
    		ForEach ($samname in $SamAccountName) {
    			if ($user = Get-AdUser -LdapFilter "(SamAccountName=$samname)") {
    				Write-Verbose ('Processing ' + $user.SamAccountName)
    				'Do stuff with ' + $user.Name
    			} else {
    				Write-Host "User $samname not found!" -fore Blue -back White
    			}
    		}
    	}
    }
    
    $results = Start-ADUserSomething -Verbose
    $results
    Start-ADUserSomething | Format-Table

    Although I don't think I would ever get locked into a design that would work like this.


    \_(ツ)_/

    Sunday, December 4, 2016 10:51 PM
  • Let me give you some background into what I am tryng to do. 

    Lets say I want to run a script against all ADUsers to get information about each one of them. I would like to either run the report to get all in the default scenario or just give the option to get information for a select group if needed. 

    So the idea is to collect all adusers if no specific one or more are specified. 


    Amit B. Agarwal

    Sunday, December 4, 2016 11:22 PM
  • Let me give you some background into what I am tryng to do. 

    Lets say I want to run a script against all ADUsers to get information about each one of them. I would like to either run the report to get all in the default scenario or just give the option to get information for a select group if needed. 

    So the idea is to collect all adusers if no specific one or more are specified. 


    Amit B. Agarwal

    Again - this is a design issue.  You need to accommodate those elements into your design.

    This will make you think real hard:

    function Start-ADUserSomething {
    	Param (
    		[Alias('Name')]
    		[Parameter(
    				   ValueFromPipelineByPropertyName = $true,
    				   ValueFromPipeline = $true,
    		)]
    		[string[]]$SamAccountName
    	)
    	Begin{
    		function DoStuff{
    			Param(
    				$SamAccountName
    			)
    			if ($user = Get-AdUser -LdapFilter "(SamAccountName=$samname)") {
    				Write-Verbose ('Processing ' + $user.SamAccountName)
    				'Do stuff with ' + $user.Name
    			} else {
    				Write-Host "User $samname not found!" -fore Blue -back White
    			}
            }
        }
    	Process {
    		if($SameAccountName){
    			ForEach ($samname in $SamAccountName) {
    				DoStuff $samname
    			}
    		}else{
    			DoStuff *
    		}
    	}
    }


    \_(ツ)_/

    Sunday, December 4, 2016 11:37 PM
  • Ahh! so basically put all of the work in a function in BEGIN and then let process just be the foreach or *. Ok thanks a bunch!

    Amit B. Agarwal

    Sunday, December 4, 2016 11:47 PM