2

I'm trying to figure out how I can use undeclared variables in a string. This is a simplified example to demonstrate my issue

[string]$sentence="My dog is $age years old"
[int]$age = 6 
$sentence | write-output

$age = 3
$sentence | write-output

Looking for this output:

   My dog is 6 years old

   My dog is 3 years old

Getting this output:

   My dog is  years old

   My dog is  years old

I've tried the following:

  "My dog is `$age years old" #My dog is $age years old
  $("My dog is `$age years old") #My dog is $age years old
  $("My dog is $age years old") #My dog is 3 years old

Is it possible in any way to make this more compact? or is this the only way?


edit: I noticed the focus is on strings. I will share my code where this was an issue. I need to migrate a bunch of VBS-scripts (>40) varying from 20 lines to 2000 lines to Powershell

VBS-file:

    Function InitializeApp

        Dim sPath
        
        ...

    End function 

Powershell Script:

$vbs=Get-Content -path $path #get content in array, one line per index
$rules=
@(
    [pscustomobject]@{
        "name"="func_start";
        "vbs" = 'Function (?<V1>\w*)'; # The regex named group V1 will initialized when match is found
        "ps" = {"Function $1() `{`n"} # Group V1 is to be inserted in replacement string
    }
)

function Invoke-TestAndReplace($line,$ruleName){
    $r_vbs=$($rules.where({$_.name -eq $ruleName}).vbs) # Function (?<V1>\w*)
    $r_ps=$($rules.where({$_.name -eq $ruleName}).ps) # {"Function $1() `{`n"}
    if( $regex = ($line | select-string -pattern $r_vbs )) {
        $0=$regex[0].Matches.Groups.Where({$_.name -eq "0"}).value # Function InitializeApp
        $1=$regex[0].Matches.Groups.Where({$_.name -eq "V1"}).value # InitializeApp
        $2=$regex[0].Matches.Groups.Where({$_.name -eq "V2"}).value #
        $r_ps = & $r_ps # Function InitializeApp() {
        $replace = $line.replace($0,$r_ps) # Function InitializeApp -> Function InitializeApp() {
    } else {$replace = $line} #keep old value
    return $replace
}
$newVBS=@()
foreach($line in $vbs){
    $line = Invoke-TestAndReplace -line $line -rulename "func_start"
}

MKZ
  • 166
  • 1
  • 12
  • Does this answer your question? [Using expanding strings as Powershell function parameters](https://stackoverflow.com/questions/51555635/using-expanding-strings-as-powershell-function-parameters) – zett42 Apr 24 '22 at 08:18

3 Answers3

4

I don't know how to do this with interpolation, but I can do it with the older .Net String.Format() method like this:

$sentence = "My dog is {0} years old."

$age = 6
[string]::Format($sentence, $age) | Write-Output
# => My dog is 6 years old

$age = 3
[string]::Format($sentence, $age) | Write-Output
# => My dog is 3 years old

[string]::Format($sentence, 12)   | Write-Output
# => My dog is 12 years old

Which we can shorten like this (thank you commenters):

$sentence = "My dog is {0} years old."

$age = 6
Write-Output ($sentence -f $age)
# => My dog is 6 years old

$age = 3
Write-Output ($sentence -f $age)
# => My dog is 3 years old

Write-Output ($sentence -f 12)
# => My dog is 12 years old

Also note the different placeholder in the template string.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • nicely done, not sure why I didn't think of this ‍♂️ uselessly overcomplicating it. in powershell the [`-f` format operator](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.2#format-operator--f) is the equivalent of `String.Format(..)` in case you want to add it to your answer – Santiago Squarzon Apr 24 '22 at 00:14
  • A good approach. You can make it more concise with `$s = "my dog is {0}"; Write-Host ($s -f 6)` – Todd Apr 24 '22 at 00:16
  • 1
    @SantiagoSquarzon I did not know about `-f`, thanks for that. I'll definitely use it more now. – Joel Coehoorn Apr 24 '22 at 00:17
1

This answer shows an overcomplicated alternative to the nice answer from Joel Coehoorn. You can use a Script Block, to store an expression (including your $age variable) and then execute it.

$sentence = { "My dog is $age years old" }
$age = 6 
& $sentence | write-output # => My dog is 6 years old

$age = 3
& $sentence | write-output # => My dog is 3 years old

If you want your script block to "remember" the value of a variable at the moment it was created you can use it's .GetNewClosure() method, for example:

$expressions = foreach($i in 0..5) {
    { "Value of `$i was $i in this iteration" }.GetNewClosure()
}
$expressions.foreach{ & $_ }
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    Thanks! This was exactly what I was looking for! In my case this was needed for some complex regex stuff. – MKZ Apr 24 '22 at 00:58
  • You can just put `| write-output` at the end of your $sentence script block instead of writing it everytime. But also strings naturally just write-output so it seems overkill to me. – Robert Cotterman Apr 25 '22 at 03:09
  • @RobertCotterman just using that because OP was using it in his question, and I know. – Santiago Squarzon Apr 25 '22 at 03:12
1

You can use ExpandString

$sentence = 'My dog is $age years old.'

$age = 4
$ExecutionContext.InvokeCommand.ExpandString($sentence)
# My dog is 4 years old.

$age = 5
$ExecutionContext.InvokeCommand.ExpandString($sentence)
# My dog is 5 years old.

You could also define a function and call the function

function sentence([int]$age) {
    "My dog is $age years old"
}

1..5 | ForEach-Object { sentence -age $_ }

# My dog is 1 years old
# My dog is 2 years old
# My dog is 3 years old
# My dog is 4 years old
# My dog is 5 years old
Daniel
  • 4,792
  • 2
  • 7
  • 20