Named format template parameters
See original GitHub issueSummary of the new feature/enhancement
Something that would be really useful in PowerShell is a readable, safe, hygienic way to format a template string given a set of key/value pairs.
Today in PowerShell, there are many ways to achieve string formatting, such as:
"Hello my name is {0}" -f "Henry""Hello my name is $Name"[string]::Format("Hello my name is {0}", $Name)- Even
"Hello my name is " + $Name
However, a scenario I see crop up from time to time is needing to read a template string from some file and wanting to insert values into it in a parameter-like way.
Today this can be done in a few ways, but none is quite ideal:
-
Use positional format templates. For example:
$str = '{0} likes the colour {1}' $str -f 'Molly','red'This is both safe (there’s no risk that the template string will have a side-effect) and hygienic (there’s no risk the formatting will replace something not intended to be a parameter), but is not readable; it’s not clear what the author has in mind in the template from
{0}and{1}, and when the template is instantiated, the values"Molly", and"red"are again decontextualised – we have no idea how they’re being used. So the greater the distance/abstraction between the template and its parameters, the harder the script is to reason about. Two more points detract here:- Multiple uses of
{0}are unclear from both the template and instantiation perspective (hard to keep track of parameters) - Heterogeneous templates, for example where some use all parameters and others don’t, are hard to manage like this
- Multiple uses of
-
Use string replacement. For example:
$str = 'VAR_NAME likes the colour VAR_COLOR' ($str -replace 'VAR_NAME','Molly') -replace 'VAR_COLOR','red'This is more readable, and still safe, but:
- It’s not hygienic, consider if the variable was just called
COLOURor ifVAR_NAMEwere replaced withVAR_COLOR. In general there’s no system of syntax at work to ensure that parts of the string aren’t intentionally replaced. - It’s not ergonomic; for each variable we must use a new
-replaceexpression - It’s inefficient; each variable causes a new string to be allocated, when we could really do all of this in one pass
- It’s not hygienic, consider if the variable was just called
-
Use PowerShell variables. For example:
$str = '$name likes the colour $color' & { $name = 'Molly'; $color = 'red'; Invoke-Expression $str }This is both hygienic and readable, since it reuses PowerShell’s own variable-driven string expansion concepts to drive the template, but:
- It’s not at all safe, since it executes the template string as given. This may cause arbitrary code execution (from within a
$(...)subexpression). So if the template is not trusted, then this cannot be used. - It’s not efficient; we must execute a PowerShell pipeline to do a simple string template instantiation
- It’s not ergonomic to do in a clean way; we are forced to instantiate variables in the calling scope, which is why I invoke it in a new scriptblock in my example, so our parameters don’t leak into the wider context
- It’s not at all safe, since it executes the template string as given. This may cause arbitrary code execution (from within a
Proposed technical implementation details (optional)
Instead of these options, I think Python sets an excellent example for string formatting. In particular:
params = {
'purpose': 'find the Holy Grail!'
}
"My quest is to {purpose}".format(**params)
Here the template string can be specified in a way that makes it easily understood in the absence of concrete parameters, while the parameters can listed in a convenient and readable order. There’s also a simple correspondence here between the general concept of splatting and named parameters in template strings.
In PowerShell today, we support template strings with positional parameters supplied as an array:
"My quest is to {0}" -f "find the Holy Grail!"
I think it’s a logical extension for -f to accept a hashtable:
"My quest is to {purpose}" -f @{
purpose = "find the Holy Grail!"
}
Just to motivate this a bit further, the reason I opened this issue is that I was confronted with a heterogeneous list of templates stored in JSON, of which some accept different parameters to others, or accept the same parameters in different order. Moreover I could imagine new entries being added that need a different format, which would make things less convenient again with positional parameters:
[
{
// Other fields...
"template": "{packageName}_{release}-1.debian.9_amd64.deb"
},
{
"template": "{packageName}_lts_{release}_{sku}..."
}
]
In such a scenario, I would love to simply parameterise the strings as above so I can do something like:
Get-Content -Raw ./packages.json |
ConvertFrom-Json |
ForEach-Object { $_.template -f @{ packageName = "powershell"; release = "7.2"; sku = "normal"; ... } }
Issue Analytics
- State:
- Created 3 years ago
- Reactions:5
- Comments:29 (9 by maintainers)

Top Related StackOverflow Question
The C# 6+ interpolated strings (e.g,
$"Expand token {name}") are the direct equivalent of PowerShell’s expandable strings, only with different syntax.The proposal in https://github.com/dotnet/runtime/issues/20317, which was rejected (“
String.Formatis already a huge source of bugs and performance issues and adding more layers is not something we really want to do. I like the general idea of a basic templating system but I think it would be better served as a higher level concept not directly on string.”) would have been the equivalent of what we already have, namely in$ExecutionContext.InvokeCommand.ExpandString(), which #11693 proposes surfacing in a friendlier - and safer - manner as a cmdlet: that is, you craft a verbatim string as you normally would an expandable one, and later expand it on demand, with the then-current values of the variables referenced and / or output from the embedded expressions.Of course, just like
-fand expandable strings happily coexist currently, with different syntax forms, there’s no reason not to implement both #11693 and the hashtable extension to-fproposed here.Given that the proposed
-fextension would be a strict superset of the .NETString.Format()method, I think it is conceptually unproblematic, as long as the relationship is clearly documented.@jhoneill good summary.
In an ideal world, I don’t think anyone using PowerShell should care about the implementation details for an operator, and I think a hashtable does map nicely, conceptually speaking. With that said, it probably introduces an opportunity for us to break something or otherwise clobber an underlying .NET functionality, which I think PowerShell has done somewhat badly in the past.
Yeah, any templating scheme is going to have some kind of input that collides with its syntax, which is why it must have an escaping mechanism. The nice thing about the existing
-fpositional template syntax is that it already covers both the template syntax and how to escape it, so it’s only one extra step to introduce naming, rather than teaching people a new operator and possibly a new syntax/mini-language underneath it.