Skip to content

String Manipulation

Note

Learn how to work with text in PowerShell: splitting, joining, replacing, formatting, and using regular expressions.

Overview

String manipulation is one of the most common tasks in PowerShell scripting. Whether you're parsing log files, formatting output, cleaning data, or building file paths, you'll constantly work with text. PowerShell provides powerful built-in methods and operators to slice, dice, search, and transform strings efficiently.

Basic String Operations

String Methods (Dot Notation)

Strings in PowerShell are .NET objects with built-in methods:

$text = "  Hello, World!  "

# Common methods
$text.Trim()           # "Hello, World!" (removes leading/trailing spaces)
$text.TrimStart()      # "Hello, World!  " (removes leading spaces only)
$text.TrimEnd()        # "  Hello, World!" (removes trailing spaces only)
$text.ToUpper()        # "  HELLO, WORLD!  "
$text.ToLower()        # "  hello, world!  "
$text.Length           # 17 (property, not a method)

# Check contents
$text.Contains("World")      # $true
$text.StartsWith("  He")     # $true
$text.EndsWith("!  ")        # $true

# Find position
$text.IndexOf("World")       # 9 (position of first match)
$text.LastIndexOf("o")       # 11 (position of last 'o')

Key Points

  • String methods don't modify the original string (strings are immutable)
  • Always assign the result to a variable: $clean = $text.Trim()
  • Methods are case-sensitive: use .ToLower() not .tolower()
  • Property vs Method: Length is a property (no parentheses), most others are methods

Splitting Strings

Using .Split() Method

# Split on a character
$csv = "John,Doe,30,Engineer"
$parts = $csv.Split(",")
# Result: @("John", "Doe", "30", "Engineer")

$parts[0]  # "John"
$parts[3]  # "Engineer"

# Split on multiple characters
$data = "apple;banana:cherry;grape"
$fruits = $data.Split(";:")
# Result: @("apple", "banana", "cherry", "grape")

# Split with options
$text = "one  two   three"
$words = $text.Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)
# Result: @("one", "two", "three") - removes empty entries from multiple spaces

Using -split Operator (PowerShell Way)

More powerful than .Split() - supports regex:

# Simple split
$text = "apple,banana,cherry"
$text -split ","
# Result: @("apple", "banana", "cherry")

# Split on whitespace (any amount)
$text = "one  two   three    four"
$text -split "\s+"
# Result: @("one", "two", "three", "four")

# Split on multiple delimiters (regex)
$text = "apple;banana:cherry,grape"
$text -split "[;:,]"
# Result: @("apple", "banana", "cherry", "grape")

# Limit number of splits
$text = "a-b-c-d-e"
$text -split "-", 3
# Result: @("a", "b", "c-d-e") - splits into max 3 parts

# Case-sensitive split
$text = "AppleXbananaXcherry"
$text -split "X"      # Case-insensitive (default): @("Apple", "banana", "cherry")
$text -csplit "X"     # Case-sensitive: @("Apple", "banana", "cherry")
$text -csplit "x"     # Case-sensitive: @("AppleXbananaXcherry") - no match

Joining Strings

Using -join Operator

# Join array elements into a single string
$items = @("apple", "banana", "cherry")

# Join with separator
$items -join ", "      # "apple, banana, cherry"
$items -join ";"       # "apple;banana;cherry"
$items -join "`n"      # Join with newline (one item per line)

# Join without separator
$letters = @("P", "o", "w", "e", "r")
$letters -join ""      # "Power"

# Join with multiple characters
$items -join " | "     # "apple | banana | cherry"

Using [string]::Join()

# Alternative method (same result as -join)
$items = @("apple", "banana", "cherry")
[string]::Join(", ", $items)  # "apple, banana, cherry"

# Join lines from a file
$lines = Get-Content C:\file.txt
$text = [string]::Join("`n", $lines)

Replacing & Removing Text

Using .Replace() Method

Simple character/string replacement:

$text = "Hello World"

# Replace characters
$text.Replace("World", "PowerShell")  # "Hello PowerShell"
$text.Replace("l", "L")               # "HeLLo WorLd"
$text.Replace(" ", "_")               # "Hello_World"

# Remove text (replace with empty string)
$text.Replace("World", "")            # "Hello " (note the space remains)
$text.Replace(" ", "")                # "HelloWorld"

# Case-sensitive
$text.Replace("hello", "Hi")          # "Hello World" (no change - case mismatch)

Using -replace Operator (Regex)

More powerful - supports patterns:

$text = "My phone: 555-1234"

# Simple replacement
$text -replace "555", "123"           # "My phone: 123-1234"

# Regex patterns
$text -replace "\d{3}-\d{4}", "XXX-XXXX"  # "My phone: XXX-XXXX" (mask phone)

# Remove all numbers
$text -replace "\d", ""                # "My phone: -"

# Remove all non-alphanumeric
"Hello, World!" -replace "[^a-zA-Z0-9]", ""  # "HelloWorld"

# Replace multiple spaces with single space
"one  two   three" -replace "\s+", " "  # "one two three"

# Case-insensitive (default)
"Hello" -replace "hello", "Hi"          # "Hi"

# Case-sensitive
"Hello" -creplace "hello", "Hi"         # "Hello" (no change)
"Hello" -creplace "Hello", "Hi"         # "Hi"

Extracting Substrings

Using .Substring() Method

$text = "PowerShell"

# Get substring starting at position (0-indexed)
$text.Substring(5)        # "Shell" (from position 5 to end)

# Get substring with specific length
$text.Substring(0, 5)     # "Power" (start at 0, take 5 characters)
$text.Substring(5, 3)     # "She" (start at 5, take 3 characters)

# Practical example: extract file extension
$filename = "report.xlsx"
$extension = $filename.Substring($filename.LastIndexOf(".") + 1)
# Result: "xlsx"

Using Array Index Notation

$text = "PowerShell"

# Single character
$text[0]              # "P" (first character)
$text[5]              # "S"
$text[-1]             # "l" (last character)
$text[-2]             # "l" (second from last)

# Range of characters
$text[0..4]           # "Power" (characters 0 through 4)
$text[5..9]           # "Shell" (characters 5 through 9)
$text[-5..-1]         # "Shell" (last 5 characters)

String Formatting

Using -f Format Operator

The PowerShell way to format strings:

# Basic formatting
$name = "Raymond"
$age = 30
"Name: {0}, Age: {1}" -f $name, $age
# Result: "Name: Raymond, Age: 30"

# Reorder parameters
"{1} is {0} years old" -f $age, $name
# Result: "Raymond is 30 years old"

# Number formatting
$price = 1234.567

"{0:C}" -f $price      # "$1,234.57" (currency)
"{0:N2}" -f $price     # "1,234.57" (number with 2 decimals)
"{0:P}" -f 0.85        # "85.00%" (percentage)
"{0:F1}" -f $price     # "1234.6" (fixed-point, 1 decimal)

# Date formatting
$date = Get-Date
"{0:yyyy-MM-dd}" -f $date              # "2025-12-15"
"{0:MM/dd/yyyy hh:mm tt}" -f $date     # "12/15/2025 02:30 PM"

# Padding and alignment
"{0,10}" -f "Right"    # "     Right" (right-aligned, 10 chars wide)
"{0,-10}" -f "Left"    # "Left      " (left-aligned, 10 chars wide)

# Zero-padding
"{0:D5}" -f 42         # "00042" (5 digits with leading zeros)

Using String Interpolation

# Double-quoted strings with variables
$name = "Raymond"
$city = "St. Louis"

"Hello, $name from $city"  # "Hello, Raymond from St. Louis"

# Expressions in $()
$items = @(1, 2, 3)
"Count: $($items.Count)"   # "Count: 3"
"Total: $($items | Measure-Object -Sum).Sum)"  # "Total: 6"

# Escape special characters
"Price: `$19.99"           # "Price: $19.99" (backtick escapes $)

Regular Expressions (Regex) Basics

Common Regex Patterns

# Email validation
$email = "user@example.com"
$email -match "^\w+@\w+\.\w+$"  # $true

# Phone number
$phone = "555-1234"
$phone -match "\d{3}-\d{4}"     # $true

# IP address
$ip = "192.168.1.1"
$ip -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"  # $true

# Extract matches
$text = "Error code: 12345"
if ($text -match "code: (\d+)") {
    $matches[1]  # "12345" (captured group)
}

Common Regex Symbols

Pattern Meaning Example
. Any character a.c matches "abc", "a1c"
\d Any digit (0-9) \d{3} matches "123"
\w Any word character (a-z, A-Z, 0-9, _) \w+ matches "hello"
\s Any whitespace \s+ matches spaces, tabs
^ Start of string ^Hello matches "Hello World"
$ End of string World$ matches "Hello World"
* Zero or more a* matches "", "a", "aaa"
+ One or more a+ matches "a", "aaa"
{n} Exactly n times \d{3} matches "123"
{n,m} Between n and m times \d{2,4} matches "12", "123", "1234"
[] Character class [abc] matches "a", "b", or "c"
[^] Negated class [^0-9] matches any non-digit

Using -match and $matches

# Test if pattern exists
$text = "Order #12345 received"
if ($text -match "#(\d+)") {
    Write-Output "Found order: $($matches[1])"  # "12345"
}

# Extract all matches with Select-String
$log = @"
Error: Code 404
Error: Code 500
Error: Code 403
"@

$log | Select-String "Code (\d+)" -AllMatches | ForEach-Object {
    $_.Matches | ForEach-Object {
        $_.Groups[1].Value  # 404, 500, 403
    }
}

Common Use Cases

Use Case 1: Parse CSV Data

What it does: Split comma-separated values and create objects

$csvLine = "John,Doe,30,Engineer,New York"
$fields = $csvLine -split ","

[PSCustomObject]@{
    FirstName = $fields[0]
    LastName = $fields[1]
    Age = [int]$fields[2]
    Title = $fields[3]
    City = $fields[4]
}

Use Case 2: Clean Up User Input

What it does: Remove extra spaces and normalize text

$userInput = "  john.doe@EXAMPLE.COM  "

# Clean and normalize
$clean = $userInput.Trim().ToLower()  # "john.doe@example.com"

# Remove all spaces
$noSpaces = $userInput -replace "\s", ""  # "john.doe@EXAMPLE.COM"

Use Case 3: Build File Paths

What it does: Safely construct file paths from components

$parts = @("C:", "Users", "raymond", "Documents", "file.txt")
$path = $parts -join "\"
# Result: "C:\Users\raymond\Documents\file.txt"

# Better: Use Join-Path
$path = Join-Path "C:\Users\raymond" "Documents\file.txt"

Use Case 4: Extract Data from Log Files

What it does: Parse log entries to extract specific information

$logLine = "[2025-12-15 14:30:45] ERROR: Connection timeout (Server: db01)"

# Extract timestamp
if ($logLine -match "\[(.*?)\]") {
    $timestamp = $matches[1]  # "2025-12-15 14:30:45"
}

# Extract server name
if ($logLine -match "Server: (\w+)") {
    $server = $matches[1]  # "db01"
}

Use Case 5: Mask Sensitive Data

What it does: Replace sensitive information with masked values

$text = "SSN: 123-45-6789, Credit Card: 1234-5678-9012-3456"

# Mask SSN
$masked = $text -replace "\d{3}-\d{2}-\d{4}", "XXX-XX-XXXX"

# Mask credit card (keep last 4 digits)
$masked = $masked -replace "(\d{4}-\d{4}-\d{4}-)(\d{4})", '****-****-****-$2'
# Result: "SSN: XXX-XX-XXXX, Credit Card: ****-****-****-3456"

Real-World Examples

Example: Parse Email Addresses from Text

Scenario: Extract all email addresses from a text block

$text = @"
Contact us at support@company.com or sales@company.com.
For urgent issues, email admin@company.com.
"@

# Extract emails using regex
$emailPattern = "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
$emails = [regex]::Matches($text, $emailPattern) | ForEach-Object { $_.Value }

# Result: @("support@company.com", "sales@company.com", "admin@company.com")

Example: Format Names Properly

Scenario: Convert names to proper case (Title Case)

function ConvertTo-ProperCase {
param([string]$Text)

# Split into words
$words = $Text.ToLower() -split "\s+"

# Capitalize first letter of each word
$properWords = $words | ForEach-Object {
    $_.Substring(0,1).ToUpper() + $_.Substring(1)
}

# Join back together
$properWords -join " "
}

ConvertTo-ProperCase "JOHN DOE"        # "John Doe"
ConvertTo-ProperCase "mary SMITH"      # "Mary Smith"

Example: Parse Server Names from URLs

Scenario: Extract server/hostname from URLs

$urls = @(
"https://server01.company.com/api/data"
"http://db-prod.company.com:8080/query"
"ftp://files.company.com/reports"
)

foreach ($url in $urls) {
if ($url -match "://([^/:]+)") {
    $server = $matches[1]
    Write-Output "Server: $server"
}
}

# Output:
# Server: server01.company.com
# Server: db-prod.company.com
# Server: files.company.com

Example: Build SQL WHERE Clause from Array

Scenario: Create SQL IN clause from PowerShell array

$usernames = @("jdoe", "msmith", "rjones")

# Wrap each in single quotes and join
$quotedUsers = $usernames | ForEach-Object { "'$_'" }
$inClause = $quotedUsers -join ", "
$sql = "SELECT * FROM Users WHERE Username IN ($inClause)"

# Result: "SELECT * FROM Users WHERE Username IN ('jdoe', 'msmith', 'rjones')"

Example: Clean Up File Names

Scenario: Remove invalid characters from file names

function Remove-InvalidFileNameChars {
param([string]$FileName)

# Get invalid characters for file names
$invalid = [System.IO.Path]::GetInvalidFileNameChars() -join ''
$pattern = "[$([regex]::Escape($invalid))]"

# Replace invalid chars with underscore
$FileName -replace $pattern, '_'
}

Remove-InvalidFileNameChars "Report: Q4 2025.xlsx"   # "Report_ Q4 2025.xlsx"
Remove-InvalidFileNameChars "Data<>File?.txt"        # "Data__File_.txt"

Tips & Tricks

Chain Methods Together

$text = "  HELLO WORLD  "
$clean = $text.Trim().ToLower()  # "hello world"

# You can chain multiple methods
$result = $text.Trim().ToLower().Replace("world", "powershell")
# Result: "hello powershell"

Use Here-Strings for Multi-Line Text

$template = @"
Dear $name,

Your order #$orderNumber has shipped.

Tracking: $trackingNumber
"@

# Variables are expanded in here-strings with double quotes

Escape Special Characters

# Backtick (`) escapes special characters
"`n"   # Newline
"`r"   # Carriage return
"`t"   # Tab
"``"   # Literal backtick
"`$"   # Literal dollar sign

Write-Output "Line 1`nLine 2`nLine 3"
# Output:
# Line 1
# Line 2
# Line 3

Test Regex Patterns Interactively

# Use -match to test patterns
$test = "test@example.com"
$test -match "^\w+@\w+\.\w+$"  # $true

# Check $matches to see captured groups
$matches

.Replace() is Case-Sensitive

$text = "Hello World"
$text.Replace("hello", "Hi")  # "Hello World" (NO change - case mismatch!)

# Use -replace for case-insensitive
$text -replace "hello", "Hi"  # "Hi World" (works!)

Regex Special Characters Need Escaping

# These characters have special meaning in regex: . * + ? ^ $ ( ) [ ] { } | \

# Wrong: trying to match a literal period
"file.txt" -match "."        # $true (matches ANY character!)

# Right: escape the period
"file.txt" -match "\."       # $true (matches literal period)

# Use [regex]::Escape() for user input
$userInput = "file.txt"
"file.txt" -match [regex]::Escape($userInput)  # Safely escapes special chars

String Concatenation vs Array Join

# Slow for large operations (creates new string each time)
$result = ""
1..1000 | ForEach-Object {
$result += "$_,"  # BAD: creates 1000 new strings
}

# Fast: build array then join once
$items = 1..1000
$result = $items -join ","  # GOOD: single operation

Watch -split With Empty Strings

# Splitting empty string returns an empty string (not empty array!)
$empty = ""
$parts = $empty -split ","
$parts.Count  # 1 (not 0!)
$parts[0]     # "" (empty string)

# Check before splitting
if ($text) {
$parts = $text -split ","
}

Working with JSON

For comprehensive JSON documentation, see the dedicated [[Working with JSON]] page.

PowerShell provides ConvertFrom-Json and ConvertTo-Json cmdlets for working with JSON data:

# JSON to PowerShell object
$json = '{"name":"Raymond","age":30}'
$obj = $json | ConvertFrom-Json
$obj.name  # "Raymond"

# PowerShell object to JSON
$data = @{server="db01"; port=5432}
$data | ConvertTo-Json

# Read JSON file (always use -Raw)
$config = Get-Content settings.json -Raw | ConvertFrom-Json

For detailed coverage including nested JSON, arrays, the -Depth parameter, file operations, and real-world examples, see Working with JSON.

Additional Resources