4

I'm trying to template some strings using Powershell string expansion. When I use a string literal it's fine, but when I pass the string as a variable it doesn't work. What do I need to do differently?

$list = Get-Process

# this works fine
$list | ForEach-Object {"$($_.ProcessName)"}

# this doesn't
$template = "$($_.ProcessName)"
$list | ForEach-Object {$template}
1
  • 1
    Have you looked at the value of $template there? It doesn't hold what you think it does. Commented Apr 9, 2015 at 16:16

4 Answers 4

4

You cannot do this with just a normal string. However, you could put the string inside a scriptblock and then invoke it with the call operator &:

$template = {"$($_.ProcessName)"}
$list | ForEach-Object {&$template}
Sign up to request clarification or add additional context in comments.

Comments

2

Let's start with the first problem.

After you run $template = "$($_.ProcessName)" you expect the value of $template to be the string $($_.ProcessName) but it isn't.

> $template = "$($_.ProcessName)"
> "[" + $template + "]"
[]

The expression in the double quotes was evaluated (and became the empty string).

You need to prevent that by using single quotes.

> $template = '$($_.ProcessName)'
> "[" + $template + "]"
[$($_.ProcessName)]

Ok. So let's try that in your foreach loop?

> $list = Get-Process
> $template = '$($_.ProcessName)'
> $list | ForEach-Object {$template}
$($_.ProcessName)
$($_.ProcessName)
$($_.ProcessName)
$($_.ProcessName)
...

Hm... not what we wanted exactly either. That's because powershell isn't evaluating the string as an expression. So we need to force it to do that.

You can do that with Invoke-Expression.

> $list = Get-Process
> $template = '$($_.ProcessName)'
> $list | ForEach-Object {Invoke-Expression $template}
procA
procB
procC
...

(You can also use a scriptblock as iCodez indicates in their answer. I can't comment on the relative merits of one approach over the other. I think the scriptblock is likely to be more extensible and useful in general though.)

1 Comment

Good explanation, but regarding whether to prefer a script block vs. Invoke-Expression: like eval in POSIX-like shells, Invoke-Expression is considered "harmful", and avoiding it is a good habit to form when other (often better) solutions are available.
1

At the point that you are generating the string in $template, $_ doesn't exist. Therefore, you are getting an empty string.

You would be better off using:

$template = "{0}"
$list | ForEach-Object { $template -f $_.ProcessName }

This uses .NET's string.Format templating. The argument passed to -f is an array of objects, so if you want more you can just comma separate them:

"{0}-{1}" -f "One", "Two"

Comments

1

You can also force the string expansion. You'll need to define the string with single quotes so it isn't expanded at that point, and then in the loop use this:

$ExecutionContext.InvokeCommand.ExpandString($template)

For what you're doing, the other answers are probably preferable. I use this if I don't know up front what variables are in the template so using -f isn't really an option.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.