String Formatting in Windows PowerShell

String Formatting in Windows PowerShell

  • Comments 1
  • Likes

Summary: Learn about using formatted strings in Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, here. Today we have another guest blog by June Blender. To read more of June’s previous posts, see these Hey, Scripting Guy Blog posts. Now, here’s June…

I recently had the opportunity to help the Windows Azure and ASP.NET product teams with some Windows PowerShell automation. Although I was supposed to be the helper, I probably learned more from the interaction than anyone else. I learned about Windows Azure and the Azure module for Windows PowerShell. And I learned a whole bunch about Windows PowerShell. I'm convinced that helping always benefits the helper. You always learn by interacting. This case was no exception.

One of the most fun things I learned was about string formatting.

When I saw the first drafts of the scripts, they had a lot of Write-Verbose calls like this one:

$VerbosePreference = "Continue"

$filepath = "C:\ps-test\test.txt"

$owner = "juneb"

$result = $true

Write-Verbose ("Created {0} by {1}. Result is {2}." –f $filepath, $owner, $result)

Here's the result:

VERBOSE: Created C:\ps-test\test.txt by juneb. Result is True.

This statement uses string formatting, that is, it creates a little template called a "format string" in the quoted string:

"Created {0} by {1}. Result is {2}."

The placeholders in the template are integers enclosed in curly braces. The integers must begin with 0 and increment by 1.

{0}, {1}, {2} ...

You can put the placeholders anywhere in the format string and use a placeholder multiple times in the same string.

To assign values to the placeholders, type -f (for format), and then type a comma-separated list of the values in the order that you want them to appear. The first value replaces the first placeholder {0} in the string, the second values replaces the {1}, and so on.

Any valid expression can be a value.

PS C:\> "Created {0} on {1} by {2}. Result is {3} times {4}." –f $filepath, [datetime]"1/12/2014", $owner, "Success", 14/7

Created C:\ps-test\test.txt on 1/12/2014 12:00:00 AM by juneb. Result is Success times 2.

You can use string formatting anywhere in Windows PowerShell where a string value is permitted. When you use a formatted string as the value of the Message parameter of Write-Verbose, you must enclose it in parentheses. Otherwise, Windows PowerShell tries to interpret -f as a parameter of Write-Verbose.

PS C:\ps-test> Write-Verbose -Message "{0}" -f "Hi" -Verbose

Write-Verbose : A parameter cannot be found that matches parameter name 'f'.

At line:1 char:30

+ Write-Verbose -Message "{0}" -f "Hi" -Verbose

+                              ~~

    + CategoryInfo          : InvalidArgument: (:) [Write-Verbose], ParameterBindingException

    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.WriteVerboseCommand

PS C:\ps-test> Write-Verbose -Message ("{0}" -f "Hi") -Verbose

VERBOSE: Hi

The critical element here is the cool -f, which is the Windows PowerShell format operator. It's described briefly in about_Operators, but the real info is on the following sites:

These docs are really worth reading. Someone put a lot of time into them and wrote extensive and very useful remarks. I've had them bookmarked for years.

To use the -f operator, you put the format string (with placeholders) on the left of the -f and the values on the right:

<format string> -f <values>

"{0} is the {1}." - f  "ScriptingWife", "best"

String formatting is really familiar to people who code in languages that don't resolve expressions or variables that occur in strings. In these languages, you use a format string or you use the plus operator (+) to concatenate strings with expressions, like in this Python example:

>>> filepath = "C:\ps-test\file.txt"

>>> owner = "juneb"

>>> result = True

>>> 

>>> "Created " + filepath + " by " + owner + ". Result is " + str(result) + "."

'Created C:\\ps-test\file.txt by juneb. Result is True.'

Good luck getting the spaces right on the first try! Of course, you can do this in Windows PowerShell, too. Fortunately, it's not required.

PS C:\> $filepath = "C:\ps-test\file.txt"

PS C:\> $owner = "juneb"

PS C:\> $result = $True

PS C:\> "Created " + $filepath + " by " + $owner + ". Result is " + $result + "."

Created C:\ps-test\file.txt by juneb. Result is True.

String formatting, -f, and string concatenation with the plus operator (+) work in Windows PowerShell, but I always thought that they were only included to help people who are familiar with other languages work successfully in Windows PowerShell.

This is fun stuff, I thought. But, honestly, there's no need to do this in Windows PowerShell, because you can include expressions right in a double-quoted string.

   Note  Single-quoted strings are different. Windows PowerShell prints them as-is with no substitutions.

PS C:\> 'Created $filepath by $owner. Result is $result.'

Created $filepath by $owner. Result is $result.

PS C:\> "Created $filepath by $owner. Result is $result."

Created C:\ps-test\file.txt by juneb. Result is True.

/NOTE]

So, when I encountered formatted strings in the Windows Azure module scripts, I replaced them with the "PowerShell way" to do it. For example, I changed:

$VerbosePreference = "Continue"

$filepath = "C:\ps-test\test.txt"

$owner = "juneb"

$result = "Success"

Write-Verbose ("Created {0} by {1}. Result is {2}." –f $filepath, $owner, $result)

   To:

$VerbosePreference = "Continue"

$filepath = "C:\ps-test\test.txt"

$owner = "juneb"

$result = "Success"

Write-Verbose "Created $filepath by $owner. Result is $result."

And it worked perfectly. So, I went through the script replacing the formatted strings with double-quoted strings.

Then I came upon statements like these, which use properties and nested properties of objects.

PS C:\> $p = Get-Process PowerShell

PS C:\>Write-Verbose ("The {0} process uses the {1} window style." -f $p.Name, $p.StartInfo.WindowStyle)

These statements return:

VERBOSE: The powershell process uses the Normal window style.

But, when I changed this Write-Verbose message to use the variables in a double-quoted string, it didn't work:

PS C:\> $p = Get-Process PowerShell

PS C:\> Write-Verbose "The $p.Name process uses the $p.StartInfo.WindowStyle window style."

VERBOSE: The System.Diagnostics.Process (powershell).Name process uses the System.Diagnostics.Process (powershell).StartInfo.WindowStyle window style.

In formatted strings, any expression can be a value. That's not true in double-quoted strings. When the values are expressions, the value type replaces the variable in the double-quoted string.

You can wrap the variable properties in another variable by using parentheses or braces. For example, change:

$<name>

   To:

$($<name>)

   Or:

${$<name>}

So, $p.Name becomes $($p.Name) or ${$p.Name}.

PS C:\> Write-Verbose "The $($p.Name) process uses the $($p.StartInfo.WindowStyle) window style."

VERBOSE: The powershell process uses the Normal window style.

It works, but it's not pretty.

Another obvious solution is to evaluate the expressions and save the results in variables before using them in the double-quoted string.

PS C:\> $p = Get-Process PowerShell

PS C:\> $pName = $p.Name

PS C:\> $pStyle = $p.StartInfo.WindowStyle

PS C:\> Write-Verbose "The $pName process uses the $pStyle window style."

VERBOSE: The powershell process uses the Normal window style.

That works, of course, but it's not more efficient. And, more importantly, when you're writing scripts that you want people to read and interpret, it's not always clearer.

Instead, the original formatted string is efficient, and when you understand string substitution, much clearer.

PS C:\> $p = Get-Process PowerShell

PS C:\>Write-Verbose ("The {0} process uses the {1} window style." -f $p.Name, $p.StartInfo.WindowStyle)

The format string is also handy when creating strings that have a syntax that is different from the Windows PowerShell syntax. In this case, Windows PowerShell tries to interpret the variable that precedes the colon as a drive name:

PS C:\> $urlHost = $url.Host

PS C:\> $ValuesPort = $values.Port

Write-Verbose ("Add-AzureVM: The url is https:// $urlHost:$valuesPort/msdeploy.axd" -Verbose

At line:1 char:49

+ Write-Verbose "Add-AzureVM: Publish Url https://$urlHost:$valuesPort/msdeploy ...

+                                                 ~~~~~~~~~

Variable reference is not valid. ':' was not followed by a valid variable name character. Consider using ${} to delimit the name.

    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException

    + FullyQualifiedErrorId : InvalidVariableReferenceWithDrive

Any of the following statements work as expected without creating extra variables, but I think that the format string makes the intended syntax much clearer than the alternatives.

Write-Verbose ("Add-AzureVM: The url is https://{0}:{1}/msdeploy.axd" -f $url.Host, $Values.Port) -Verbose

Write-Verbose ("Add-AzureVM: The url is https://$($url.Host):$($Values.Port)/msdeploy.axd" -Verbose

Write-Verbose ("Add-AzureVM: The url is https://${$url.Host}:${$Values.Port}/msdeploy.axd" -Verbose

Write-Verbose ("Add-AzureVM: The url is https://" + $url.Host + ":" + $Values.Port + "/msdeploy.axd") -Verbose

So, when the message string includes simple variables, I use double-quoted strings. When the message string includes a property value that is used repeatedly, I save the property value in a new variable and use the variable a double-quoted string.

But when a string includes multiple expressions or an alternate syntax, I prefer a formatted string.

And I learned an important lesson about using formatted strings and the -f operator in Windows PowerShell. It's not just an interesting artifact. It's really useful.

~June

Thanks for writing this informative post, June!

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Thank you June! I liked the article although I personally avoid the -f operator like it carries the plague. I can't stand the syntax and I like subexpressions.