Advanced Functions

This post is part of the Second Wednesday Demo Session, Click here for more info about additional demo posts.

If you have a set of commands or code that you want to reuse multiple times in a script or in your session you can place the code in a function.
This allows you to call the function instead of typing the same piece of code over and over again.

Just as an example, we’ll use Get-Process and display the Process Name and Company property.


PS > $Name = "powershell"
PS > $process = Get-Process $Name
PS > Write-Host ProcessName: $Process.Name

ProcessName: powershell

PS > Write-Host Company: $Process.Company

Company: Microsoft Corporation

Instead of typing the lines above everytime we want to check the Name and Company of a process we can place the code in a function.


function Get-Company([string]$Name) {
  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

Now we can use the function instead.


PS > Get-Company -Name powershell

ProcessName: powershell
Company: Microsoft Corporation

Calling a function in PowerShell is similar to calling one of the built-in cmdlets. You type the functions name followed by one or more parameters.

You can also use Get-Help to find out additional information about a function, just as you do with CmdLets.


PS > Get-Help Get-Company

Get-Company [[-Name] ]

CmdLets in powershell usually have a set of common parameters such as Verbose, Debug, OutVariable and so on. You can add these additional parameters by using the [CmdletBinding()] attribute.


function Get-Company {
  [cmdletbinding()]
  param([string]$Name)

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

If we use the Get-Help CmdLet additional parameters are available in our function.


PS > Get-Help Get-Company

Get-Company [[-Name] ] [-Verbose] [-Debug] [-ErrorAction ] 
[-WarningAction ] [-ErrorVariable ] [-WarningVariable ] 
[-OutVariable ] [-OutBuffer ]

It’s also possible to set a parameter as mandatory. Here’s an example.


function Get-Company {
  param(
    [Parameter(Mandatory = $true)]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

If we call the function without a value to the Name parameter we are automatically prompted for it.


PS > Get-Company

cmdlet Get-Company at command pipeline position 1
Supply values for the following parameters:
Name: powershell

ProcessName: powershell
Company: Microsoft Corporation

If we want to validate the input to a parameter we can use the [ValidateSet()] attribute. Here’s an example.


function Get-Company {
  param(
    [Parameter(Mandatory = $true)]
    [ValidateSet("powershell","notepad")]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

The example above only accepts “powershell” or “notepad” as input.


PS > Get-Company -Name powershell

ProcessName: powershell
Company: Microsoft Corporation

If we type anything else an error occurs.


PS > Get-Company -Name calc

Get-Company : Cannot validate argument on parameter 'Name'. The argument "calc" does 
not belong to the set "powershell,notepad" specified by the ValidateSet attribute. 
Supply an argument that is in the set and then try the command again.
At line:1 char:18
+ Get-Company -Name <<<<  calc
    + CategoryInfo          : InvalidData: (:) [Get-Company], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Get-Company

We can also position the parameter. This applies when we provide parameter values without specifying the parameter.


function Get-Company {
  param(
    [Parameter(Position = 0)]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

PS > Get-Company powershell

ProcessName: powershell
Company: Microsoft Corporation

We can also let the function accept input from a pipeline by using ValueFromPipeLine.


function Get-Company {
  param(
    [Parameter(
      Position = 0,
      ValueFromPipeLine= $true)]
    [string]$Name
  )

  $process = Get-Process $Name
  Write-Host ProcessName: $Process.Name
  Write-Host Company: $Process.Company
}

Now we can use a pipeline when calling the function.


PS > "powershell" | Get-Company
ProcessName: powershell
Company: Microsoft Corporation

What if we send two inputs through the pipeline?


PS > "powershell","winword" | Get-Company

ProcessName: WINWORD
Company: Microsoft Corporation

Note that only the last input value was returned by the function. Adding a Process block solves this.


function Get-Company {
  param(
    [Parameter(
      Position = 0,
      ValueFromPipeLine= $true)]
    [string]$Name
  )
  Process {
    $process = Get-Process $Name
    Write-Host ProcessName: $Process.Name
    Write-Host Company: $Process.Company
  }
}

Now we can send additional values through the pipeline.


PS > "powershell","winword" | Get-Company

ProcessName: powershell
Company: Microsoft Corporation
ProcessName: WINWORD
Company: Microsoft Corporation

We can also use propertynames from an object passed through a pipeline. Here's an example.


function Get-Company {
  param(
    [Parameter(
      ValueFromPipeLineByPropertyName= $true)]
    [string]$Name,
    [Parameter(
      ValueFromPipeLineByPropertyName= $true)]
    [string]$Company
  )
  Process {
    Write-Host ProcessName: $Name
    Write-Host Company: $Company
  }
}

Now we can use Get-Process and pipe the objects to our function as shown below.


PS > Get-Process powershell | Get-Company

ProcessName: powershell
Company: Microsoft Corporation

PS > Get-Process powershell,winword | Get-Company

ProcessName: powershell
Company: Microsoft Corporation
ProcessName: WINWORD
Company: Microsoft Corporation

In an earlier example we took a look at how to add common parameters to a function, but how do we use them?
Well, it turns out that advanced functions in powershell give us access to the $pscmdlet automatic variable.
The variable exposes support for Verbose, Whatif and more. The function below calls the variable and displays it's properties.


function Get-PSCmdLet {
  [cmdletbinding()]
  param()

  $psCmdLet
}

If we call the function, the variable properties are displayed.


PS > Get-PSCmdLet


ParameterSetName     : __AllParameterSets
MyInvocation         : System.Management.Automation.Invoc
InvokeCommand        : System.Management.Automation.Comma
Host                 : System.Management.Automation.Inter
SessionState         : System.Management.Automation.Sessi
Events               : System.Management.Automation.PSLoc
JobRepository        : System.Management.Automation.JobRe
InvokeProvider       : System.Management.Automation.Provi
Stopping             : False
CommandRuntime       : Get-PSCmdLet
CurrentPSTransaction :
CommandOrigin        : Runspace

If we pipe the object to Get-Member we'll see all Methods and Properties available on the object.


PS > Get-PSCmdLet | Get-Member


   TypeName: System.Management.Automation.PSScriptCmdlet

Name                                MemberType Definition
----                                ---------- ----------
CurrentProviderLocation             Method     System.Managem
Dispose                             Method     System.Void Di
Equals                              Method     bool Equals(Sy
GetDynamicParameters                Method     System.Object
GetHashCode                         Method     int GetHashCod
GetResolvedProviderPathFromPSPath   Method     System.Collect
GetResourceString                   Method     string GetReso
GetType                             Method     type GetType()
GetUnresolvedProviderPathFromPSPath Method     string GetUnre
GetVariableValue                    Method     System.Object
Invoke                              Method     System.Collect
ShouldContinue                      Method     bool ShouldCon
ShouldProcess                       Method     bool ShouldPro
ThrowTerminatingError               Method     System.Void Th
ToString                            Method     string ToStrin
TransactionAvailable                Method     bool Transacti
WriteCommandDetail                  Method     System.Void Wr
WriteDebug                          Method     System.Void Wr
WriteError                          Method     System.Void Wr
WriteObject                         Method     System.Void Wr
WriteProgress                       Method     System.Void Wr
WriteVerbose                        Method     System.Void Wr
WriteWarning                        Method     System.Void Wr
CommandOrigin                       Property   System.Managem
CommandRuntime                      Property   System.Managem
CurrentPSTransaction                Property   System.Managem
Events                              Property   System.Managem
Host                                Property   System.Managem
InvokeCommand                       Property   System.Managem
InvokeProvider                      Property   System.Managem
JobRepository                       Property   System.Managem
MyInvocation                        Property   System.Managem
ParameterSetName                    Property   System.String
SessionState                        Property   System.Managem
Stopping                            Property   System.Boolean

Now let's see how we can use this in our function. Say we want to write to the Verbose message stream. We can do this by simply using the WriteVerbose() method as shown below.


function Get-Company {
  [cmdletbinding()]
  param([string]$Name)

  $process = Get-Process $Name
  $psCmdLet.WriteVerbose("ProcessName: $($Process.Name)")
  $psCmdLet.WriteVerbose("Company: $($Process.Company)")
}

When we call the function nothing seems to happen.


PS > Get-Company -Name powershell

But if we add the -Verbose parameter the verbose messages are returned.


PS > Get-Company -Name powershell -Verbose

VERBOSE: ProcessName: powershell
VERBOSE: Company: Microsoft Corporation

Rating 4.50 out of 5
[?]