mao
6/30/2017 - 4:08 PM

Template for Powershell function with constants block which you can override while invoking and add parameters of any kind from Command line

Template for Powershell function with constants block which you can override while invoking and add parameters of any kind from Command line

<#
    .SYNOPSIS
        Template of Function which takes any number of parameters 


    .DESCRIPTION
        Template of Function which takes any number of parameters. 
        Function has default parameter block $defaults which can be overridden by command line parameters.
        Function accumulates command line parameters and merges them with default parameters to the $__ENV.env hashtable.


    .PARAMETER ShowProperty
        Select one of 5 views of resulting parameters: Env, Named, Positional, Default and All (TODO)


    .INPUTS
        Does not accept inputs from the pipeline


    .OUTPUTS
        Outputs [PSCustomObject] with all the data about resulting parameters set.


    .NOTES
        (c) 2017 Andriy Melnyk  https://guthub.com/TurboBasic


    .LINK
        #Merge-Hastables


    .FUNCTIONALITY
        Use this for testing and learning how Powershell processes advanced function parameters.  Due to fully working and universal 
        feature set it is easy and convenient to use as a template function


    .EXAMPLE
        In all examples we assume that the Function doesn't have positional parameters and default parameters block looks as follows:
          $defaults = @{   
              SERVER         = 'http://localhost'
              PORT           = 8080
              SOME_CONSTANT  = 0xFF
              PRODUCTION     = $False
              showProperty   = 'Env'
          }

        PS C:\> Abstract-FunctionWithConfigBlockTemplate

        Name                           Value
        ----                           -----
        PORT                           8080
        SOME_CONSTANT                  255
        SERVER                         http://localhost
        PRODUCTION                     False
        showProperty                   Env


    .EXAMPLE
        By default, resulting set includes only named parameters:

        PS C:\> Abstract-FunctionWithConfigBlockTemplate -a 2 $true 4

        Name                           Value
        ----                           -----
        PRODUCTION                     False
        SERVER                         http://localhost
        SOME_CONSTANT                  255
        showProperty                   Env
        PORT                           8080
        a                              2


    .EXAMPLE
        PS C:\> Abstract-FunctionWithConfigBlockTemplate -server 'https://8.8.8.8', 'http://local.dev' -port 80 -production

        Name                           Value
        ----                           -----
        PRODUCTION                     True
        SERVER                         {https://8.8.8.8, http://local.dev}
        SOME_CONSTANT                  255
        showProperty                   Env
        PORT                           80


    .EXAMPLE
        Show non-default Named parameters

        PS C:\> Abstract-FunctionWithConfigBlockTemplate -server 'https://8.8.8.8','http://localhost' -port 80 -production -showproperty named

        Key              Value
        ---              -----
        __restParameters {-server, https://8.8.8.8 http://localhost, -port, 80...}
        server            {https://8.8.8.8, http://localhost}
        port             80
        production       True
        showproperty     named

#>

Function Abstract-FunctionWithConfigBlockTemplate {
    [OUTPUTTYPE( [string[]] )]
    [CMDLETBINDING( PositionalBinding=$False )] 
    PARAM(

      #region Constants block. 
          [PARAMETER()]
          [VALIDATESCRIPT({
            If( $_ -is [System.Collections.Hashtable] ) 
                { $True } 
            Else 
                { Throw "'$_' should be a Hashtable!" } 
          })]
          $defaults = @{   
              SERVER         = 'http://localhost'
              PORT           = 8080
              SOME_CONSTANT  = 0xFF
              PRODUCTION     = $False
              showProperty   = 'Env'
          },

            # listEnv        = $True 
            # listNamed      = $False
            # listDefault    = $False
            # listPositional = $False

      #endregion 
   

      #region Standard parameters

       <# 
            add `[PARAMETER( Position=<number> )]` if you need positional parameter.
            In this case custom parameters will start to accumulate after the last positional parameter
            and you will have to include postional parameters to command line
            
            Eg. if you have 2 positional parameters  
                [PARAMETER(Position=0)] $a
                [PARAMETER(Position=1)] $b
            then the bindings for the following commands will look as follows:
            
            PS> Abstract-FunctionWithConfigBlockTemplate -customArg 2 3 4
            $__ENV.customArg = 2
            $a = 3
            $b = 4
            
            PS> Abstract-FunctionWithConfigBlockTemplate 2 3 4
            $__ENV.anonymousParameter = 4
            $a = 2
            $b = 3
        #>
      
          [PARAMETER( ValueFromPipeline )]     # add `Position=<number>` if needed
          [Int[]] 
          $SomeIntegerParameter = 137,

          [PARAMETER( )]                       # add `Position=<number>` if needed
          [String] 
          $SomeTextParameter = 'Default text',
      
      #endregion


      #region Custom parameters

          [PARAMETER( ValueFromRemainingArguments )]
          [Object[]] 
          $__restParameters

      #endregion  
    )

  BEGIN {

      # Current parameter binding state
      #
      # Ready:  Previous parameter completely read. Ready to read next named or positional 
      #         parameter
      #
      # ParameterName: 
      #         Name of parameter has been read, expecting its value, if no value $True is default
      #
      enum ParameterBindingState { 
              Ready
              ParameterName
      }

      # Global parameter binding state depends on current parameter state and current text token
      # (this is current parameter name) and previously read parameter name, if any
      $bindingState = @{
        State =   [ParameterBindingState]::Ready
        Name  =   ''
      }


      $Parameters = Foreach ($Parameter in $__restParameters) {
          if( $Parameter -match '^-([a-z_][a-z0-9_]*)$' ) { 

              $bindingState.Name = $Matches[1]
              New-Variable -Name $bindingState.Name -Value $True
              $PSBoundParameters.Add( $bindingState.Name, $True ) | Out-Null
              $bindingState.State = [ParameterBindingState]::ParameterName

          } elseif( $bindingState.State -eq [ParameterBindingState]::ParameterName ) { 

              $Value = $foreach.Current
              Set-Variable -Name $bindingState.Name -Value $Value
              $PSBoundParameters[$bindingState.Name] = $Value
              $bindingState.State = [ParameterBindingState]::Ready
              $bindingState.Name = ''

          } elseif( $bindingState.State -eq [ParameterBindingState]::Ready ) { 
              $Parameter 
          }
      }


      $__ENV = @{}
      $__ENV.Add('Named', $psBoundParameters)
      $__ENV.Add('Positional', $Parameters)
      $__ENV.Add('Default', $defaults )      
  }


  PROCESS {    
    $null = 'Do something with arguments'
  }


  END {
    $__ENV.Add( 'Env', (Merge-Hashtables $__ENV.Default $__ENV.Named) )
    $__ENV.Env.Remove('__restParameters')

    # $__ENV | Format-Table -Autosize -Wrap `
    #            Name, @{ Label = "Value"; Expression = { ConvertTo-JSON $_.Value } } 
    
    $includeProperties = @()
    $includeProperties += ,'Env'        * $__ENV.Env.listEnv
    $includeProperties += ,'Named'      * $__ENV.Env.listNamed
    $includeProperties += ,'Default'    * $__ENV.Env.listDefault
    $includeProperties += ,'Positional' * $__ENV.Env.listPositional
    ([psCustomObject]$__ENV)  | Select -Property $includeProperties | Out-Null

    ([psCustomObject]$__ENV)  | Select -ExpandProperty $__ENV.Env.showProperty

  }
} 
# some short examples
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2  @('cdf',5),1)
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2  @('cdf',5),1).Positional
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2  @('cdf',5),1).Positional[2]
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2  @('cdf',5),1).Positional[3][1]
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2  @('cdf',5),1).Named
# (Abstract-FunctionWithConfigBlockTemplate 'tty' -x -15 -yy @(1,2),2  -zz -zzn @('cdf',5),1 -zlast).Named