Tag Archives: Get-Content

Find and Replace Text with PowerShell

magnifying-glass I’ve just finished up a series of tweets with a follower who had a question about finding and replacing a bit of data in a text file. In his case it was a web.config file but it really could be any text file that you can view in PowerShell. In PowerShell 3.0 and later this isn’t too difficult. Although I haven’t tested with really large files to determine if there are any limitations. Before you try anything I’m going to show you, please have a backup copy of of the original file.

Let’s say I have a file like this:

I need to change the IP source address in the connection string. As you probably guessed Get-Content is going to come into play. Normally when you use Get-Content you get an array of strings which adds a little complexity. But starting in v3 Get-Content has a -Raw parameter which writes the entire file as a single string. If we have a single string then we can easily use -Replace.

Your replacement pattern could even be a regular expression pattern. This could even be done as a one-liner:

For many of you, especially with unstructured text files, this technique should get the job done. But in this case, because the text file is also an XML document we could tackle the problem a different way. This is especially useful if you have several changes you want to make to the file.

First, create an XML document from the file.

We can navigate the document until we find the entry we need to change.

xmlconfig

We can assign a new value with revised connection string:

All that remains is to save the file back to disk.

Or, if you didn’t necessarily know the structure of your document, you could use an XPath query with Select-XML to find the node and then change the value.

The node properties will vary depending on your XML file so you will need to look at the object.
xmlnode

The XML approach is a bit more involved but is predictable and you don’t have to save the file until you have the XML just the way you want it.

One thing I will say about both techniques is that the more you understand how to use regular expressions in PowerShell the more you can accomplish. If you are feeling a little wobbly on the topic, there is an entire chapter on regular expressions in PowerShell in Depth.

Friday Fun Get Content Words

Recently I was tracking down a bug in script for a client. The problem turned out to be a simple typo. I could have easily avoided that by using Set-StrictMode, which I do now, but that’s not what this is about. What I realized I wanted was a way to look at all the for “words” in a script. If I could look at them sorted, then typos would jump out. At least in theory.

My plan was to get the content of a text file or script, use a regular expression pattern to identify all the “words” and then get a sorted and unique list. Here’s what I came up with.


Function Get-ContentWords {

[cmdletbinding()]

Param (
[Parameter(Position=0,Mandatory=$True,
HelpMessage="Enter the filename for your text file",
ValueFromPipeline=$True)]
[string]$Path
)

Begin {
Set-StrictMode -Version 2.0

Write-Verbose "Starting $($myinvocation.mycommand)"

#define a regular expression pattern to detect "words"
[regex]$word="\b\S+\b"
}

Process {

if ($path.gettype().Name -eq "FileInfo") {
#$Path is a file object
Write-Verbose "Getting content from $($Path.Fullname)"
$content=Get-Content -Path $path.Fullname
}
else {
#$Path is a string
Write-Verbose "Getting content from $path"
$content=get-content -Path $Path
}

#add a little information
$stats=$content | Measure-Object -Word
Write-Verbose "Found approximately $($stats.words) words"

#write sorted unique values
$word.Matches($content) | select Value -unique | sort Value
}

End {
Write-Verbose "Ending $($myinvocation.mycommand)"
}

} #close function

The function uses Get-Content to retrieve the content (what else?!) of the specified file. At the beginning of the function I defined a regular expression object to find “words”.


#define a regular expression pattern to detect "words"
[regex]$word="\b\S+\b"

This is an intentionally broad pattern that searches for anything not a space. The \b element indicates a word boundary. Because this is a REGEX object, I can do a bit more than using a basic -match operator. Instead I’ll use the Matches() method which will return a collection of match objects. I can pipe these to Select-Object retrieving just the Value property. I also use the -Unique parameter to filter out duplicates. Finally the values are sorted.


$word.Matches($content) | select Value -unique | sort Value

The matches and filtering are NOT case-sensitive, which is fine for me. With the list I can see where I might have used write-host instead of Write-Host and go back to clean up my code. Let me show you how this works. Here’s a demo script.


#Requires -version 2.0

$comp = Read-Host "Enter a computer name"

write-host "Querying services on $comp" -fore Cyan
$svc = get-service -comp $comp

$msg = "I found {0} services on $comp" -f $svc.count
Write-Host "Results" -fore Green
Write-Host $mgs -fore Green

The script has some case inconsistencies as well as a typo. I’ve dot sourced the function in my PowerShell session. Here’s what I end up with.

For best results, you need to make sure there are spaces around commands that use the = sign. But now I can scan through the list and pick out potential problems. Sure, Set-StrictMode would help with variable typos but if I had errors in say comment based help, that wouldn’t help. Maybe you’ll find this useful in your scripting work, maybe not. But I hope you learned a few things about working with REGEX objects and unique properties.

Download Get-ContentWords and enjoy.

ByValue, I Think He’s Got It

Recently I responded to an email from a student seeking clarification about the difference between ByValue and ByProperty when it comes to parameter binding. This is what makes pipelined expressions work in Windows PowerShell. When you look at cmdlet help, you’ll see that some parameters accept pipeline binding, which is what you are looking for. Often this means you don’t need to resort to a ForEach-Object construct. Here’s an example.

ByValue means if I see something I’ll use it. ByProperty means I’m looking for a specific object property. For example, Get-Service uses the Name property to find a service.

This parameter accepts both types of binding. This means you can do this:

This is an example of byValue. Get-Service sees something in the pipeline and assumes it is a service name. However, for Get-Service this would also work.

$all[0] is a service object with name property which when piped to Get-Service, finds the property and binds to it. Here’s another example that shows binding by property name can be from any object, not just a service object.

This is a one line expression that leverages the pipeline and uses some helpful (I hope) techniques. The first part using Get-Content retrieves the list of computer names from the text file. Because the text file might have blank lines and some computers might be offline, each name is piped to Where-Object which will only pass on names that exist (skipping blanks) and that can be pinged. To speed things up I’m only sending 2 pings. Now the fun part. I could use ForEach-Object and pass $_ as the value for -Computername. But according to help, this parameter accepts binding by property name.

So I’ll take the computername value coming from Where-Object and use a hash table with Select-Object to define a new “property” name, called Computername. I also take the liberty of trimming off any leading or trailing spaces, just in case. Now I have an object with a property called Computername that is piped to Get-Service which binds on the computername property. The rest is merely formatting.

Look for opportunities to bind by parameter, which means reading cmdlet help which is a good habit to have regardless.