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"
}

1

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

3 Comments

nicely done, not sure why I didn't think of this 🤦‍♂️ uselessly overcomplicating it. in powershell the -f format operator is the equivalent of String.Format(..) in case you want to add it to your answer
A good approach. You can make it more concise with $s = "my dog is {0}"; Write-Host ($s -f 6)
@SantiagoSquarzon I did not know about -f, thanks for that. I'll definitely use it more now.
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{ & $_ }

3 Comments

Thanks! This was exactly what I was looking for! In my case this was needed for some complex regex stuff.
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.
@RobertCotterman just using that because OP was using it in his question, and I know.
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

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.