Freeform Functions

By
James Brundage | MVP
May 12, 2026
Jack finds out about PowerShell freeform functions.

We all yearn for freedom.

We want to be free from tyranny. We want to be free to live. We want to be free to do things we enjoy.

Some of us yearn to be free of PowerShell's parameter structure.

We might want to pass a prompt to AI.

We might want to pass parameters to an exe without rewriting them.

We might want to rewrite them.

There are all sorts of reasons you might want to be free of parameter binding in PowerShell.

Whatever yours might be, I'm going to highlight three approaches to making freeform functions in PowerShell.

  1. Freeform functions
  2. Freeform Filters
  3. Freeform Cmdlets

These functions will accept any input and any parameters.

This makes it so the parameter binding never fails.

This can be beneficial, and it can be problematic.

Freeform Functions

Back in the days of PowerShell 1.0, functions didn't have complex parameters.

They didn't have validation. They didn't have inline help. They were fairly simple functions.

They just had an object pipeline of input, and any variables in the input would be bound by position.

The PowerShell language is backwards compatible, so this low-level capability never went away.

It's always been there and should always be there.

With all of that in mind, here's how we write a freeform function using this fundamental trick:

# A freeform function
function freeform {@($input) + @($args)}

# A quick example with pipeline and arguments
1..3 | freeform "I want to break free!" "God Knows" "God Knows" "I want to break free!"

One quick note: $input can only be read once.

Once you read the input, the objects break free.

With that in mind, I'd recommend a slight variation of freeform:

# A freeform function
function freeform {
   $allInput = @($input)
   $allInput + $args
}

# A quick example with pipeline and arguments
1..3 | freeform "I want to break free!" "God Knows" "God Knows" "I want to break free!"

Of course, you're free to do whatever you'd like. That's one of the joys of freedom.

Freeform Function Performance

Another joy of freeform functions is performance.

The PowerShell parameter binder is cool, and it is complex. Writing PowerShell in this format is many orders of magnitude faster than a function with complex parameter binding.

If you want extraordinarily fast functions, this trick is your best friend.

Don't believe me? Try piping a million items into that function. It's pretty snappy.

This performance benefit also happens because we are not having to do a begin, process, and end block. Everything is happening as soon as the million items all came thru the pipeline.

Freeform functions are wonderful this way. But what if we wanted to process each item as they came in, and still have flexible arguments?

That's what filters are for.

Freeform Filters

Filters are another part of PowerShell arcana.

They were also introduced in v1 and never went away.

Let's make a freeform filter

filter freeform {
   $_ # Output our input
   $args # Output our arguments
}

# Run our filter three times.
# We will see 1,2,3 followed by the word "filter"
1..3 | freeform "filter"

Filters are also decently fast.

Fun factoid: filters are looked up before functions. This gives them a very slight performance edge on functions. If you're piping in lots of items, this slight edge will disappear.

While both of these techniques are quite fast, speed isn't the only thing that matters.

Sometimes we might want to have a freeform function that has additional parameters.

Freeform Script Cmdlets

PowerShell v2 brought the full parameter binding capabilities to PowerShell functions. Sometimes these functions are called "advanced functions". They were originally called "Script Cmdlets". If we want tab completion and types and we still want a freeform function, we can get there with a pair of parameters.

function freeform {
   [CmdletBinding(PositionalBinding=$false)]
   param(
   # This parameter will take any arguments
   [Parameter(ValueFromRemainingArguments)]
   [Alias('Arguments', 'Argument', 'Args')]
   [PSObject[]]
   $ArgumentList,

   # This parameter will take any input
   [Parameter(ValueFromPipeline)]
   [PSObject[]]
   $InputObject
   )

   # `$InputObject` would contain the _last_ input
   # so we can use `$Input` to store any piped input
   $AllInput = @($input)
   # If there was no piped input, we did not pipe input.
   # We can still populate `$AllInput` based off the `$InputObject`
   if (-not $allInput) { $allInput += $InputObject }

   $allInput
   $ArgumentList
}

1..3 | freeform "Freedom!"

This last freeform format might be a bit slower, but it's a lot more friendly to inline help and tab completion. We can create additional parameters if we want. They can be strongly typed and validated. We can even create additional parameter sets. We are free to do whatever we want.

Freeform Fun

Freedom from the PowerShell parameter parser can be a wonderful thing.

We can treat parameters as natural language.

We can make our parameters into a Domain Specific Language (DSL).

For an amazing module that uses this trick, check out Turtle. Turtle uses freeform functions to give us a logo like syntax within PowerShell.

Try making some freeform functions and enjoy the freedom they bring.

Hope this helps,

James