Skip to content

PowerShell Advanced Functions

How to create PowerShell advanced functions with [CmdletBinding()] to enable common parameters, parameter validation, pipeline support, and lifecycle blocks like begin, process, and end for cmdlet-like behavior.

An advanced function (often called a script cmdlet) is a PowerShell function that behaves identically to compiled binary cmdlets. By decorating a standard function with the [CmdletBinding()] attribute, you unlock standard cmdlet features, including common parameters (-Verbose, -Debug, -ErrorAction), parameter validation attributes, pipeline integration, and lifecycle execution blocks (begin, process, and end).

The [CmdletBinding()] Attribute

Adding [CmdletBinding()] directly above the param() block transforms a standard function into an advanced function. This attribute automatically exposes PowerShell's common parameters without requiring you to declare them manually:

  • Information and Debugging: -Verbose, -Debug, -InformationAction, -InformationVariable
  • Error and Warning Handling: -ErrorAction, -ErrorVariable, -WarningAction, -WarningVariable
  • Output and Pipeline: -OutVariable, -OutBuffer, -PipelineVariable
  • Impact Mitigation: -WhatIf, -Confirm (when SupportsShouldProcess = $true is specified)

Furthermore, [CmdletBinding()] provides access to the $PSCmdlet runtime variable and forces the function into strict mode, throwing errors if callers pass undeclared parameters.

Enabling Verbose Output with [CmdletBinding()]

The following script illustrates how [CmdletBinding()] enables the built-in -Verbose switch.

test-servicehealth.ps1powershell
function Test-ServiceHealth {
    [CmdletBinding()]
    param (
        [string]$ServiceName
    )

    Write-Verbose "Querying system for service status: $ServiceName"
    $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue

    if ($null -eq $service) {
        Write-Warning "Service '$ServiceName' does not exist on this host."
        return
    }

    Write-Verbose "Service found. Current state: $($service.Status)"
    [PSCustomObject]@{
        Name   = $service.Name
        Status = $service.Status
    }
}

Test-ServiceHealth -ServiceName "Spooler"

Test-ServiceHealth -ServiceName "Spooler" -Verbose

where:

  • [CmdletBinding()] declares this function as an advanced function, automatically injecting standard common parameters.
  • param ([string]$ServiceName) defines the function parameter.
  • Write-Verbose emits output to the verbose stream (stream 4). This message is suppressed by default and only displays when -Verbose is passed at execution time.
  • Write-Warning emits a non-terminating warning message to stream 3.
Consolepowershell
./test-servicehealth.ps1
Outputplaintext
function Test-ServiceHealth {
    [CmdletBinding()]
    param (
        [string]$ServiceName
    )

    Write-Verbose "Querying system for service status: $ServiceName"
    $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue

    if ($null -eq $service) {
        Write-Warning "Service '$ServiceName' does not exist on this host."
        return
    }

    Write-Verbose "Service found. Current state: $($service.Status)"
    [PSCustomObject]@{
        Name   = $service.Name
        Status = $service.Status
    }
}

Test-ServiceHealth -ServiceName "Spooler"

Test-ServiceHealth -ServiceName "Spooler" -Verbose

Parameter Validation Attributes

Advanced functions allow you to enforce strict data contracts using parameter validation attributes. These attributes validate arguments before any script logic in the function body executes, eliminating the need for defensive boilerplate code.

Key validation attributes include:

  • [ValidateNotNullOrEmpty()] ensures the supplied value is neither $null nor an empty string or empty collection.
  • [ValidateSet('Val1', 'Val2', ...)] restricts allowable values to a predefined list. Tab completion automatically surfaces these valid options.
  • [ValidateRange(Min, Max)] restricts numeric values or collections of numbers to a specific numerical range.
  • [ValidatePattern('Regex')] verifies the parameter string matches a regular expression.
  • [ValidateCount(Min, Max)] enforces the minimum and maximum number of elements in an array.

Enforcing Parameter Constraints

The following script defines a deployment function with validation decorators.

deploy-application.ps1powershell
function Set-AppEnvironment {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationName,

        [Parameter(Mandatory = $true)]
        [ValidateSet("Development", "Staging", "Production")]
        [string]$Environment,

        [ValidateRange(1, 16)]
        [int]$InstanceCount = 1
    )

    Write-Host "Deploying $InstanceCount instance(s) of '$ApplicationName' to '$Environment'..." -ForegroundColor Cyan
}

Set-AppEnvironment -ApplicationName "AuthService" -Environment "Staging" -InstanceCount 4

where:

  • [Parameter(Mandatory = $true)] causes PowerShell to prompt the user if the argument is omitted from the command line.
  • [ValidateNotNullOrEmpty()] rejects empty strings such as "" or whitespace-only inputs.
  • [ValidateSet(...)] restricts the $Environment parameter strictly to "Development", "Staging", or "Production".
  • [ValidateRange(1, 16)] blocks any $InstanceCount below 1 or above 16.
  • [int]$InstanceCount = 1 provides a default value if the parameter is omitted.
Consolepowershell
./deploy-application.ps1
Outputplaintext
Deploying 4 instance(s) of 'AuthService' to 'Staging'...

When an invalid argument violates a constraint, PowerShell terminates execution during parameter binding before running the function body:

Consolepowershell
Set-AppEnvironment -ApplicationName "AuthService" -Environment "Sandbox" -InstanceCount 4
Outputplaintext
Set-AppEnvironment : Cannot validate argument on parameter 'Environment'. The argument "Sandbox" does not 
belong to the set "Development,Staging,Production" specified by the ValidateSet attribute. Supply an argument 
that is in the set and then try the command again.

Pipeline Input: Begin, Process, and End Blocks

To accept objects from the PowerShell pipeline, parameters must declare pipeline attributes, and the function body must be structured using lifecycle execution blocks: begin, process, and end.

Pipeline Lifecycle Blocks

  1. begin {} runs once per function call before any pipeline data arrives. Use this block for initialization tasks (e.g., establishing database connections, opening log files, resetting tracking variables).
  2. process {} runs once for each object passed through the pipeline. If data is passed as a standard parameter argument rather than via the pipeline, the process block runs exactly once. All per-item business logic belongs here.
  3. end {} runs once after all pipeline items have passed through the process block. Use this block for teardown and cleanup operations (e.g., closing file streams, flushing buffers, displaying aggregate statistics).
+-------------------------------------------------------+
| Pipeline Stream: Object1 -> Object2 -> Object3        |
+-------------------------------------------------------+
                           |
                           v
        +-------------------------------------+
        | begin { }     (Executes 1 time)     |
        +-------------------------------------+
                           |
                           v
        +-------------------------------------+
        | process { }   (Executes 3 times)    |
        +-------------------------------------+
                           |
                           v
        +-------------------------------------+
        | end { }       (Executes 1 time)     |
        +-------------------------------------+

Configuring Pipeline-Aware Parameters

Parameters accept pipeline input using one or both of these properties:

  • ValueFromPipeline = $true binds input objects matching the parameter's type.
  • ValueFromPipelineByPropertyName = $true binds to a specific property of an input object if the property name matches the parameter name.

Simple Pipeline Example

The following function squares each number piped into it.

pipeline-input.ps1powershell
function Get-SquaredValue {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [int]$Number
    )

    process {
        $Number * $Number
    }
}

1, 2, 3, 4 | Get-SquaredValue

where:

  • [Parameter(ValueFromPipeline = $true)] allows $Number to receive values piped into the function.
  • process { } runs once per pipeline item.
Outputplaintext
1
4
9
16

Implementing a Pipeline-Enabled Function

The following script accepts server names from both standard parameter arguments and the pipeline, inspecting each host in the process block.

test-networkhost.ps1powershell
function Test-NetworkHost {
    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("CN", "ServerName")]
        [string[]]$ComputerName
    )

    begin {
        Write-Verbose "=== Initializing Connectivity Verification Suite ==="
        $results = [System.Collections.Generic.List[PSCustomObject]]::new()
    }

    process {
        foreach ($computer in $ComputerName) {
            Write-Verbose "Testing reachability for: $computer"
            $pingSuccess = Test-Connection -TargetName $computer -Count 1 -Quiet

            $results.Add([PSCustomObject]@{
                ComputerName = $computer
                Reachable    = $pingSuccess
                Timestamp    = (Get-Date)
            })
        }
    }

    end {
        Write-Verbose "=== Finalizing and Emitting Results ==="
        $results
    }
}

"127.0.0.1", "localhost" | Test-NetworkHost

[PSCustomObject]@{ ComputerName = "127.0.0.1" } | Test-NetworkHost

where:

  • ValueFromPipeline = $true permits direct piping of raw strings into the $ComputerName parameter.
  • ValueFromPipelineByPropertyName = $true extracts the ComputerName property automatically from piped objects.
  • [Alias("CN", "ServerName")] maps alternative property names (such as CN from Active Directory objects) directly to $ComputerName.
  • [string[]]$ComputerName is typed as an array to handle multiple values whether piped individually or passed via -ComputerName host1,host2.
  • begin {} initializes the $results collection before any pipeline records arrive.
  • process {} iterates over the current pipeline object bound to $ComputerName.
  • end {} emits the collected results back to the pipeline after stream processing finishes.
Consolepowershell
pwsh ./test-networkhost.ps1
Outputplaintext
ComputerName Reachable Timestamp
------------ --------- ---------
127.0.0.1         True 9/28/2026 12:15:18 AM
localhost         True 9/28/2026 12:15:18 AM
127.0.0.1         True 9/28/2026 12:15:18 AM

Practical Usage: Building Cmdlet-Like Functions

Combining [CmdletBinding()], validation, and pipeline blocks produces a function that feels indistinguishable from a built-in cmdlet.

Temperature Conversion with Pipeline Support

convert-temperature.ps1powershell
function Convert-Temperature {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [double]$Celsius,

        [ValidateSet("Fahrenheit", "Kelvin")]
        [string]$To = "Fahrenheit"
    )

    process {
        switch ($To) {
            "Fahrenheit" { [PSCustomObject]@{ Celsius = $Celsius; Fahrenheit = ($Celsius * 9/5) + 32 } }
            "Kelvin"     { [PSCustomObject]@{ Celsius = $Celsius; Kelvin = $Celsius + 273.15 } }
        }
    }
}

20, 25, 30 | Convert-Temperature -To Fahrenheit
Outputplaintext
Celsius Fahrenheit
------- ----------
  20.00      68.00
  25.00      77.00
  30.00      86.00

Supporting -WhatIf and -Confirm

Production administrative scripts should avoid making destructive changes blindly. By specifying SupportsShouldProcess = $true inside [CmdletBinding()], PowerShell adds -WhatIf and -Confirm parameters to your function automatically, allowing users to preview or verify actions before execution.

Inside the function, wrap dangerous actions in an if ($PSCmdlet.ShouldProcess(...)) condition.

clear-tempdata.ps1powershell
function Clear-ApplicationTempData {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$FolderPath
    )

    process {
        if (-not (Test-Path -LiteralPath $FolderPath)) {
            Write-Warning "Target path '$FolderPath' does not exist."
            return
        }

        $targetFiles = Get-ChildItem -Path $FolderPath -File -Recurse -ErrorAction SilentlyContinue

        foreach ($file in $targetFiles) {
            if ($PSCmdlet.ShouldProcess($file.FullName, "Delete permanent application temp file")) {
                Remove-Item -LiteralPath $file.FullName -Force
                Write-Verbose "Successfully removed: $($file.FullName)"
            }
        }
    }
}

Clear-ApplicationTempData -FolderPath "C:\AppLogs\Temp" -WhatIf

where:

  • SupportsShouldProcess = $true implements standard -WhatIf and -Confirm functionality on the function.
  • ConfirmImpact = 'High' informs PowerShell that this operation is potentially destructive. If the system's $ConfirmPreference is set to High or lower, the function automatically prompts for confirmation even if -Confirm was not explicitly passed.
  • $PSCmdlet.ShouldProcess($target, $action) intercepts execution. If -WhatIf was passed, it prints the target and action without executing the code inside the if block. If -Confirm was passed, it prompts the user with [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend.
Consolepowershell
./clear-tempdata.ps1
Outputplaintext
What if: Performing the operation "Delete permanent application temp file" on target "C:\AppLogs\Temp\cache_01.dat".
What if: Performing the operation "Delete permanent application temp file" on target "C:\AppLogs\Temp\session_99.tmp".

Best Practices

  • Use [CmdletBinding()] on any function intended for reuse across scripts or modules.
  • Always structure pipeline-enabled functions with begin, process, and end blocks.
  • Prefer parameter validation attributes over manual if checks to keep functions self-documenting and safe.
  • Implement SupportsShouldProcess for any function that modifies system state.
  • Avoid $global: variables; use parameters and return values for data flow.
Navigation