Powershell - Overiding Arguments Relase definition - powershell

I am trying to create a Release definition in TFS 2017 with powershell script
I am runing a powershell script that has to find a path of a setParameters.xml file which it does and it suppose to over ride all the tokens but the it just ignores the loop and the arguments stay the same.
Script code:
$vars = Get-ChildItem -path env:*
# read in the setParameters file
$contents = Get-Content -Path $paramsFilePath
# perform a regex replacement
$newContents = "";
$contents | % {
$line = $_
if ($_ -match "__(\w+)__") {
$setting = Get-ChildItem -path env:* | ? { $_.Name -eq $Matches[1] }
if ($setting) {
Write-Verbose -Verbose ("Replacing key {0} with value from environment" -f $setting.Name)
$line = $_ -replace "__(\w+)__", $setting.Value
}
}
$newContents += $line + [Environment]::NewLine
}
Why is the loop being skiped and how could i fix it so it overwrites the arguments in SetParameters.xml file?

For those interested I have ditched this powershell and used Replace Token step in Release definition.
I think its a more efficient way to replace tokens

Related

Hidden variable Password parameter - powershell 4.0

I am creating a release definition with a powershell script to replace a file with env variables from the release definition it works but It doesn't seem to catch the password variable which is hidden in the release definition. is there a way to tell powershell to look for hidden variables?
UPDATE: Here is the script it finds all the variables in $paramsFilePath that are not hidden my password in In environmental variables in Release definition is hidden and the script doesn't find it.
param(
[string]$paramsFilePath,
)
Write-Verbose -Verbose "Entering script Replace-SetParameters.ps1"
Write-Verbose -Verbose ("Path to SetParametersFile: {0}" -f $paramsFilePath)
# get the environment variables
$vars = Get-ChildItem -path env:*
# read in the setParameters file
$contents = Get-Content -Path $paramsFilePath
# perform a regex replacement
$newContents = "";
$contents | % {
$line = $_
if ($_ -match "__(\w+)__") {
$setting = Get-ChildItem -path env:* | ? { $_.Name -eq $Matches[1] }
if ($setting) {
Write-Verbose -Verbose ("Replacing key {0} with value from environment" -f $setting.Name)
$line = $_ -replace "__(\w+)__", $setting.Value
}
}
$newContents += $line + [Environment]::NewLine
}
Write-Verbose -Verbose "Overwriting SetParameters file with new values"
Set-Content $paramsFilePath -Value $newContents
Write-Verbose -Verbose "Exiting script Replace-SetParameters.ps1"
Unlike the normal variable, the password you are trying to get is secret variable.
Secret Variables
We recommend that you make the variable Secret if it contains a
password, keys, or some other kind of data that you need to avoid
exposing.
The variable replacement we do is on the inputs on the tasks, we don't parse the scripts. To use secret variables you will have to take those as inputs into your script we explicitly do not populate those into the environment. You could take a look at this discuss: Use hidden / secret variables in commands

Monitor a command and wait for it to complete before proceeding to next command?

I have written a PowerShell script that will:
grab all txt files from a directory
perform a line-by-line assessment of the first file (grabbing headers and appending, appending data to each line in file, saving to an output file)
for subsequent files, grab body (excluding header), append data, then add to output file
The problem is in the use of Add-Content where the process hangs so certain files don't get written because the output file is in use. I added a function (based on recommendations found in various places on StackExchange) that test the output file to determine if it is available for read-write. This seems like a 'brute-force' approach.
Is there a way to monitor the actual Add-Content process launched by PowerShell to identify when it is complete? Or is there some other way to disaggregate the code as written to use the process control commands in PowerShell?
Sample:
function IsFileAccessible([String]$FullFileName) {
[Boolean]$IsAccessible = $false
try {
[IO.File]::OpenWrite($FullFileName).Close();
$IsAccessible = $true
} catch {
$IsAccessible = $false
}
return $IsAccessible
}
cd '[filepath]'
del old_output.type
$filearray = #()
$files = Get-ChildItem '[filepath]' -Filter "*.txt"
$outfile = 'new_output.type'
for ($i=0; $i -lt $files.Count; $i++) {
# Define variables
$lastWriteTime = $files[$i].LastWriteTime
# Define process steps for appending data
filter Add-Time {"$_$lastWriteTime"}
if ($i -eq 0) {
$lines = Get-Content $files[$i]
for ($j=0;$j -lt $lines.Count; $j++) {
if ($j -eq 0) {
$appended_txt = 'New_Header'
filter Add-Header{"$_$appended_txt"}
$lines[$j] | Add-Header | Add-Content $outfile
} else {
$lines[$j] | Add-Time | Add-Content $outfile
}
}
} else {
do {
$ErrorActionPreference = 'SilentlyContinue'
$test = IsFileAccessible('[filepath-new_output.type]')
echo 'file open'
} until ($test -eq 'True')
$ErrorActionPreference = 'Continue'
echo 'okay'
(Get-Content $files[$i].FullName | Select-Object -Skip 1) |
Add-Time | Add-Content $outfile
}
}

Powershell Host File edit

Guys i'm having some issues converting my Perl script to powershell, I need some help. In the host file of our machines, we have all of the URL's to our test environments blocked. In my PERL script, based on which environment is selected, it will comment out the line of the environment selected to allow access and block others so the testers can't mistakenly do things in the wrong environment.
I need help converting to powershell
Below is what I have in PERL:
sub editHosts {
print "Editing hosts file...\n";
my $file = 'C:\\Windows\\System32\\Drivers\\etc\\hosts';
my $data = readFile($file);
my #lines = split /\n/, $data;
my $row = '1';
open (FILE, ">$file") or die "Cannot open $file\n";
foreach my $line (#lines) {
if ($line =~ m/$web/) {
print FILE '#'."$line\n"; }
else {
if ($row > '21') {
$line =~ s/^\#*127\.0\.0\.1/127\.0\.0\.1/;
$line =~ s/[#;].*$//s; }
print FILE "$line\n"; }
$row++;
}
close(FILE);
}
Here is what i've tried in Powershell:
foreach ($line in get-content "C:\windows\system32\drivers\etc\hosts") {
if ($line -contains $web) {
$line + "#"
}
I've tried variation including set-content with what used to be in the host file, etc.
Any help would be appreciated!
Thanks,
Grant
-contains is a "set" operator, not a substring operator. Try .Contains() or -like.
This will comment out lines matching the variable $word, while removing # from non-matches (except the header):
function Edit-Hosts ([string]$Web, $File = "C:\windows\system32\drivers\etc\hosts") {
#If file exists and $web is not empty/whitespace
if((Test-Path -Path $file -PathType Leaf) -and $web.Trim()) {
$row = 1
(Get-Content -Path $file) | ForEach-Object {
if($_ -like "*$web*") {
#Matched PROD, comment out line
"#$($_)"
} else {
#No match. If past header = remove comment
if($row -gt 21) { $_ -replace '^#' } else { $_ }
}
$row++
} | Set-Content -Path $file
} else {
Write-Error -Category InvalidArgument -Message "'$file' doesn't exist or Web-parameter is empty"
}
}
Usage:
Edit-Hosts -Web "PROD"
This is a similar answer to Frode F.'s answer, but I'm not yet able to comment to add my 2c worth, so have to provide an alternative answer instead.
It looks like one of the gotchas moving from perl to PowerShell, in this example, is that when we get the content of the file using Get-Content it is an "offline" copy, i.e. any edits are not made directly to the file itself. One approach is to compile the new content to the whole file and then write that back to disk.
I suppose that the print FILE "some text\n"; construct in perl might be similar to "some text" | Out-File $filename -Encoding ascii -Append in PowerShell, albeit you would use the latter either (1) to write line-by-line to a new/empty file or (2) accept that you are appending to existing content.
Two other things about editing the hosts file:
Be sure to make sure that your hosts file is ASCII encoded; I have caused a major outage for a key enterprise application (50k+ users) in learning that...
You may need to remember to run your PowerShell / PowerShell ISE by right-clicking and choosing Run as Administrator else you might not be able to modify the file.
Anyway, here's a version of the previous answer using Out-File:
$FileName = "C:\windows\system32\drivers\etc\hosts"
$web = "PROD"
# Get "offline" copy of file contents
$FileContent = Get-Content $FileName
# The following creates an empty file and returns a file
# object (type [System.IO.FileInfo])
$EmptyFile = New-Item -Path $FileName -ItemType File -Force
foreach($Line in $FileContent) {
if($Line -match "$web") {
"# $Line" | Out-File $EmptyFile -Append -Encoding ascii
} else {
"$Line" | Out-File $EmptyFile -Append -Encoding ascii
}
}
Edit
The ($Line -match "$web") takes whatever is in the $web variable and treats it as a regular expression. In my example I was assuming that you were just wanting to match a simple text string, but you might well be trying to match an IP address, etc. You have a couple of options:
Use ($Line -like "*$web*") instead.
Convert what is in $web to be an escaped regex, i.e. one that will match literally. Do this with ($Line -match [Regex]::Escape($web)).
You also wanted to strip off comments from any line past row 21 of the hosts file, should that line not match $web. In perl you have used the s substitution operator; the PowerShell equivalent is -replace.
So... here is an updated version of that foreach loop:
$LineCount = 1
foreach($Line in $FileContent) {
if($Line -match [Regex]::Escape($web) {
# ADD comment to any matched line
$Line = "#" + $Line
} elseif($LineCount -gt 21) {
# Uncomment the other lines
$Line = $Line -replace '^[# ]+',''
}
# Remove 'stacked up' comment characters, if any
$Line = $Line -replace '[#]+','#'
$Line | Out-File $EmptyFile -Append -Encoding ascii
$LineCount++
}
More Information
Are there good references for moving from Perl to Powershell?
How to use operator '-replace' in PowerShell to replace strings of texts with special characters and replace successfully
about_Comparison_Operators
http://www.comp.leeds.ac.uk/Perl/sandtr.html
If you wanted to verify what was in there and then add entries, you could use the below which is designed to be ran interactively and returns any existing entries you specify in the varibles:
Note: the `t is powershell's in script method for 'Tab' command.
$hostscontent
# Script to Verify and Add Host File Entries
$hostfile = gc 'C:\Windows\System32\drivers\etc\hosts'
$hostscontent1 = $hostfile | select-string "autodiscover.XXX.co.uk"
$hostscontent2 = $hostfile | select-string "webmail.XXX.co.uk"
$1 = "XX.XX.XXX.XX`tautodiscover.XXX.co.uk"
$2 = "webmail.XXX.co.uk"
# Replace this machines path with a path to your list of machines e.g. $machines = gc \\machine\machines.txt
$machines = gc 'c:\mytestmachine.txt'
ForEach ($machine in $machines) {
If ($hostscontent1 -ne $null) {
Start-Sleep -Seconds 1
Write-Host "$machine Already has Entry $1" -ForegroundColor Green
} Else {
Write-Host "Adding Entry $1 for $machine" -ForegroundColor Green
Start-Sleep -Seconds 1
Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "XX.XX.XXX.XX`tautodiscover.XXX.co.uk" -Force
}
If ($hostscontent2 -ne $null) {
Start-Sleep -Seconds 1
Write-Host "$machine Already has Entry $2" -ForegroundColor Green
} Else {
Write-Host "Adding Entry $2 for $machine" -ForegroundColor Green
Start-Sleep -Seconds 1
Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "XX.XX.XXX.XX`twebmail.XXX.co.uk" -Force
}
}

Powershell variable scope and temporary files within the context of a VS2015 MSBuild project

Question
Two separate Powershell scripts, run from two separate projects (Chrome and Firefox), apparently write to the same temporary file.
This doesn't happen often, apparently once every few months or so. When it does happen, though, the two separate projects each end up with their manifest.json and package.json files munged together, such that each file will have random lines from the other file interspersed in it.
Any ideas why this occurs?
Details
I have a VS2015 solution with 25 projects in it. One of the projects is called "Deploy", and has declared dependencies on multiple other projects. The two dependencies of significance to this question are the "ChromeExtension" and "FirefoxAddOn" projects.
Each of these projects contains a build target to set the version information in the appropriate manifest file. Here is the Chrome target:
<Target Name="SetVersion">
<PropertyGroup>
<PowerShellExe>powershell.exe</PowerShellExe>
<PreBuildScript>$(SolutionDir)powershell\ChromePreBuild.ps1</PreBuildScript>
</PropertyGroup>
<Exec Command="$(PowerShellExe) -NonInteractive -ExecutionPolicy Unrestricted -command "& { &'$(PreBuildScript)' -solutionDir '$(SolutionDir)\' }"" />
</Target>
And the Firefox target is the same except it executes the script, FirefoxPreBuild.ps1.
The scripts are relatively simple. The Chrome script:
param($solutionDir)
. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
. "${solutionDir}powershell\ReplaceName.ps1"
. "${solutionDir}powershell\ChromeOptions.ps1"
Write-Host "Performing pre-build actions for Chrome add-on $chromeExtensionVersion"
ReplaceVersion "${solutionDir}ChromeExtension\manifest.json" $chromeExtensionVersion
ReplaceName "${solutionDir}ChromeExtension\manifest.json" "$chromeExtensionName"
And the Firefox script:
param($solutionDir)
. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
Write-Host "Performing pre-build actions for Firefox add-on $firefoxExtensionVersion"
ReplaceVersion "${solutionDir}FirefoxAddOn\package.json" $firefoxExtensionVersion
The ReplaceVersion function, called from both scripts, is pretty ugly, but gets the job done:
function ReplaceVersion {
$file = $args[0]
$replacementVersion = $args[1]
Write-Host "- Updating version number in $file to ""$replacementVersion"""
$tmp = [System.IO.Path]::GetTempFileName();
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
Get-Content $file | Foreach-Object -process {
$line = $_
if ( ($line -match 'AssemblyVersion.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'AssemblyFileVersion.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'AppFolderName.*(\d+\.\d+\.\d+\.\d+)') -or
($line -match 'SupportedSyncFeatureSetRevision\s*=\s*(\d+);') -or
($line -match 'this.AddinName = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
($line -match 'this.Text = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
($line -match '"version":.*"(\d+\.\d+\.\d+\.?\d*)"') ) {
$version = $matches[1]
$line = $line -replace "$version", "$replacementVersion"
}
$line | Add-Content $tmp
}
#replace the old file with the new one
Remove-Item -force $file
Move-Item $tmp $file -Force -Confirm:$false
}
And the ReplaceName funcion (Chrome-only) is, unimaginatively and non-DRY, the exact same thing, except with a different pattern to match on:
function ReplaceName {
$file = $args[0]
$replacementName = $args[1]
Write-Host "- Updating extension name in $file to ""$replacementName"""
$tmp = [System.IO.Path]::GetTempFileName();
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
Get-Content $file | Foreach-Object -process {
$line = $_
if ( ($line -match '"name":.*"(.*)"') ) {
$name = [regex]::escape($matches[1])
$line = $line -replace "$name", "$replacementName"
}
$line | Add-Content $tmp
}
#replace the old file with the new one
Remove-Item -force $file
Move-Item $tmp $file -Force -Confirm:$false
}
You should remove that part from scripts:
if(Test-Path $tmp -PathType Leaf) {
Remove-Item $tmp
}
As stated in documentation
Creates a uniquely named, zero-byte temporary file on disk and returns the full path of that file.
[System.IO.Path]::GetTempFileName() will create empty file for you, so you does not have to remove and re-create it.
If you look at source of [System.IO.Path]::GetTempFileName(), then you can see that it implemented by calling to GetTempFileName WinAPI function with uUnique equals to zero. Here is relevant part of documentation:
If uUnique is zero, the function attempts to form a unique file name using the current system time. If the file already exists, the number is increased by one and the functions tests if this file already exists. This continues until a unique filename is found; the function creates a file by that name and closes it.
As you can see, GetTempFileName use file existence as signal to create different file name. By removing file you open possibility that subsequent call will return the same file name for temporary file.

How can I replace every occurrence of a String in a file with PowerShell?

Using PowerShell, I want to replace all exact occurrences of [MYID] in a given file with MyValue. What is the easiest way to do so?
Use (V3 version):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
Or for V2:
(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
I prefer using the File-class of .NET and its static methods as seen in the following example.
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
This has the advantage of working with a single String instead of a String-array as with Get-Content. The methods also take care of the encoding of the file (UTF-8 BOM, etc.) without you having to take care most of the time.
Also the methods don't mess up the line endings (Unix line endings that might be used) in contrast to an algorithm using Get-Content and piping through to Set-Content.
So for me: Fewer things that could break over the years.
A little-known thing when using .NET classes is that when you have typed in "[System.IO.File]::" in the PowerShell window you can press the Tab key to step through the methods there.
(Get-Content file.txt) |
Foreach-Object {$_ -replace '\[MYID\]','MyValue'} |
Out-File file.txt
Note the parentheses around (Get-Content file.txt) is required:
Without the parenthesis the content is read, one line at a time, and flows down the pipeline until it reaches out-file or set-content, which tries to write to the same file, but it's already open by get-content and you get an error. The parenthesis causes the operation of content reading to be performed once (open, read and close). Only then when all lines have been read, they are piped one at a time and when they reach the last command in the pipeline they can be written to the file. It's the same as $content=content; $content | where ...
The one above only runs for "One File" only, but you can also run this for multiple files within your folder:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '[MYID]', 'MyValue' }) |
Set-Content $_
}
I found a little known but amazingly cool way to do it from Payette's Windows Powershell in Action. You can reference files like variables, similar to $env:path, but you need to add the curly braces.
${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
You could try something like this:
$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path
$newText = $text -replace $word,$replacement
$newText > $path
This is what I use, but it is slow on large text files.
get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile
If you are going to be replacing strings in large text files and speed is a concern, look into using System.IO.StreamReader and System.IO.StreamWriter.
try
{
$reader = [System.IO.StreamReader] $pathToFile
$data = $reader.ReadToEnd()
$reader.close()
}
finally
{
if ($reader -ne $null)
{
$reader.dispose()
}
}
$data = $data -replace $stringToReplace, $replaceWith
try
{
$writer = [System.IO.StreamWriter] $pathToFile
$writer.write($data)
$writer.close()
}
finally
{
if ($writer -ne $null)
{
$writer.dispose()
}
}
(The code above has not been tested.)
There is probably a more elegant way to use StreamReader and StreamWriter for replacing text in a document, but that should give you a good starting point.
Credit to #rominator007
I wrapped it into a function (because you may want to use it again)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
}
NOTE: This is NOT case sensitive!!!!!
See this post: String.Replace ignoring case
If You Need to Replace Strings in Multiple Files:
It should be noted that the different methods posted here can be wildly different with regard to the time it takes to complete. For me, I regularly have large numbers of small files. To test what is most performant, I extracted 5.52 GB (5,933,604,999 bytes) of XML in 40,693 separate files and ran through three of the answers I found here:
## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files)
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
#### Test 1 - Plain Replace
$start = Get-Date
foreach ($xml in $xmls) {
(Get-Content $xml).replace("'", " ") | Set-Content $xml
}
$end = Get-Date
New-TimeSpan –Start $Start –End $End
# TotalMinutes: 103.725113128333
#### Test 2 - Replace with -Raw
$start = Get-Date
foreach ($xml in $xmls) {
(Get-Content $xml -Raw).replace("'", " ") | Set-Content $xml
}
$end = Get-Date
New-TimeSpan –Start $Start –End $End
# TotalMinutes: 10.1600227983333
#### Test 3 - .NET, System.IO
$start = Get-Date
foreach ($xml in $xmls) {
$txt = [System.IO.File]::ReadAllText("$xml").Replace("'"," ")
[System.IO.File]::WriteAllText("$xml", $txt)
}
$end = Get-Date
New-TimeSpan –Start $Start –End $End
# TotalMinutes: 5.83619516833333
Since this comes up often, I defined a function for it. I defaulted to case-sensitive, regex-based matching, but I included switches for targeting literal text and ignoring case.
# Find and replace text in each pipeline string. Omit the -Replace parameter to delete
# text instead. Use the -SimpleMatch switch to work with literal text instead of regular
# expressions. Comparisons are case-sensitive unless the -IgnoreCase switch is used.
Filter Edit-String {
Param([string]$Find, [string]$Replace='', [switch]$SimpleMatch, [switch]$IgnoreCase)
if ($SimpleMatch) {
if ($IgnoreCase) {
return $_.Replace($Find, $Replace,
[System.StringComparison]::OrdinalIgnoreCase)
}
return $_.Replace($Find, $Replace)
}
if ($IgnoreCase) {
return $_ -replace $Find, $Replace
}
return $_ -creplace $Find, $Replace
}
Set-Alias replace Edit-String
Set-Alias sc Set-Content
Usage
# 1 file
$f = a.txt; gc $f | replace '[MYID]' 'MyValue' -SimpleMatch | sc $f
# 0 to many files
gci *.txt | % { gc $_ | replace '\[MYID\]' 'MyValue' | sc $_ }
# Several replacements chained together
... | replace '[1-9]' T | replace a b -IgnoreCase | replace 'delete me' | ...
# Alias cheat sheet
# gci Get-ChildItem
# gc Get-Content
# sc Set-Conent
# % ForEach-Object
This worked for me using the current working directory in PowerShell. You need to use the FullName property, or it won't work in PowerShell version 5. I needed to change the target .NET framework version in ALL my CSPROJ files.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
A bit old and different, as I needed to change a certain line in all instances of a particular file name.
Also, Set-Content was not returning consistent results, so I had to resort to Out-File.
Code below:
$FileName =''
$OldLine = ''
$NewLine = ''
$Drives = Get-PSDrive -PSProvider FileSystem
foreach ($Drive in $Drives) {
Push-Location $Drive.Root
Get-ChildItem -Filter "$FileName" -Recurse | ForEach {
(Get-Content $_.FullName).Replace($OldLine, $NewLine) | Out-File $_.FullName
}
Pop-Location
}
This is what worked best for me on this PowerShell version:
Major.Minor.Build.Revision
5.1.16299.98
Here's a fairly simple one that supports multiline regular expressions, multiple files (using the pipeline), specifying output encoding, etc. Not recommended for very large files due to the ReadAllText method.
# Update-FileText.ps1
#requires -version 2
<#
.SYNOPSIS
Updates text in files using a regular expression.
.DESCRIPTION
Updates text in files using a regular expression.
.PARAMETER Pattern
Specifies the regular expression pattern.
.PARAMETER Replacement
Specifies the regular expression replacement pattern.
.PARAMETER Path
Specifies the path to one or more files. Wildcards are not supported. Each file is read entirely into memory to support multi-line searching and replacing, so performance may be slow for large files.
.PARAMETER CaseSensitive
Specifies case-sensitive matching. The default is to ignore case.
.PARAMETER SimpleMatch
Specifies a simple match rather than a regular expression match (i.e., the Pattern parameter specifies a simple string rather than a regular expression).
.PARAMETER Multiline
Changes the meaning of ^ and $ so they match at the beginning and end, respectively, of any line, and not just the beginning and end of the entire file. The default is that ^ and $, respectively, match the beginning and end of the entire file.
.PARAMETER UnixText
Causes $ to match only linefeed (\n) characters. By default, $ matches carriage return+linefeed (\r\n). (Windows-based text files usually use \r\n as line terminators, while Unix-based text files usually use only \n.)
.PARAMETER Overwrite
Overwrites a file by creating a temporary file containing all replacements and then replacing the original file with the temporary file. The default is to output but not overwrite.
.PARAMETER Force
Allows overwriting of read-only files. Note that this parameter cannot override security restrictions.
.PARAMETER Encoding
Specifies the encoding for the file when -Overwrite is used. Possible values for this parameter are ASCII, BigEndianUnicode, Unicode, UTF32, UTF7, and UTF8. The default value is ASCII.
.INPUTS
System.IO.FileInfo.
.OUTPUTS
System.String (single-line file) or System.String[] (file with more than one line) without the -Overwrite parameter, or nothing with the -Overwrite parameter.
.LINK
about_Regular_Expressions
.EXAMPLE
C:\> Update-FileText.ps1 '(Ferb) and (Phineas)' '$2 and $1' Story.txt
This command replaces the text 'Ferb and Phineas' with the text 'Phineas and Ferb' in the file Story.txt and outputs the content. Note that the pattern and replacement strings are enclosed in single quotes to prevent variable expansion.
.EXAMPLE
C:\> Update-FileText.ps1 'Perry' 'Agent P' Story2.txt -Overwrite
This command replaces the text 'Perry' with the text 'Agent P' in the file Story2.txt.
#>
[CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
param(
[Parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)]
[String[]] $Path,
[Parameter(Mandatory = $true,Position = 1)]
[String] $Pattern,
[Parameter(Mandatory = $true,Position = 2)]
[AllowEmptyString()]
[String] $Replacement,
[Switch] $CaseSensitive,
[Switch] $SimpleMatch,
[Switch] $Multiline,
[Switch] $UnixText,
[Switch] $Overwrite,
[Switch] $Force,
[ValidateSet("ASCII","BigEndianUnicode","Unicode","UTF32","UTF7","UTF8")]
[String] $Encoding = "ASCII"
)
begin {
function Get-TempName {
param(
$path
)
do {
$tempName = Join-Path $path ([IO.Path]::GetRandomFilename())
}
while ( Test-Path $tempName )
$tempName
}
if ( $SimpleMatch ) {
$Pattern = [Regex]::Escape($Pattern)
}
else {
if ( -not $UnixText ) {
$Pattern = $Pattern -replace '(?<!\\)\$','\r$'
}
}
function New-Regex {
$regexOpts = [Text.RegularExpressions.RegexOptions]::None
if ( -not $CaseSensitive ) {
$regexOpts = $regexOpts -bor [Text.RegularExpressions.RegexOptions]::IgnoreCase
}
if ( $Multiline ) {
$regexOpts = $regexOpts -bor [Text.RegularExpressions.RegexOptions]::Multiline
}
New-Object Text.RegularExpressions.Regex $Pattern,$regexOpts
}
$Regex = New-Regex
function Update-FileText {
param(
$path
)
$pathInfo = Resolve-Path -LiteralPath $path
if ( $pathInfo ) {
if ( (Get-Item $pathInfo).GetType().FullName -eq "System.IO.FileInfo" ) {
$fullName = $pathInfo.Path
Write-Verbose "Reading '$fullName'"
$text = [IO.File]::ReadAllText($fullName)
Write-Verbose "Finished reading '$fullName'"
if ( -not $Overwrite ) {
$regex.Replace($text,$Replacement)
}
else {
$tempName = Get-TempName (Split-Path $fullName -Parent)
Set-Content $tempName $null -Confirm:$false
if ( $? ) {
Write-Verbose "Created file '$tempName'"
try {
Write-Verbose "Started writing '$tempName'"
[IO.File]::WriteAllText("$tempName",$Regex.Replace($text,$Replacement),[Text.Encoding]::$Encoding)
Write-Verbose "Finished writing '$tempName'"
Write-Verbose "Started copying '$tempName' to '$fullName'"
Copy-Item $tempName $fullName -Force:$Force -ErrorAction Continue
if ( $? ) {
Write-Verbose "Finished copying '$tempName' to '$fullName'"
}
Remove-Item $tempName
if ( $? ) {
Write-Verbose "Removed file '$tempName'"
}
}
catch [Management.Automation.MethodInvocationException] {
Write-Error $Error[0]
}
}
}
}
else {
Write-Error "The item '$path' must be a file in the file system." -Category InvalidType
}
}
}
}
process {
foreach ( $PathItem in $Path ) {
if ( $Overwrite ) {
if ( $PSCmdlet.ShouldProcess("'$PathItem'","Overwrite file") ) {
Update-FileText $PathItem
}
}
else {
Update-FileText $PathItem
}
}
}
Also available as a gist on Github.
Sample to replace all strings inside a folder:
$path=$args[0]
$oldString=$args[1]
$newString=$args[2]
Get-ChildItem -Path $path -Recurse -File |
ForEach-Object {
(Get-Content $_.FullName).replace($oldString,$newString) | Set-Content $_.FullName
}
Small correction for the Set-Content command. If the searched string is not found the Set-Content command will blank (empty) the target file.
You can first verify if the string you are looking for exist or not. If not it will not replace anything.
If (select-string -path "c:\Windows\System32\drivers\etc\hosts" -pattern "String to look for") `
{(Get-Content c:\Windows\System32\drivers\etc\hosts).replace('String to look for', 'String to replace with') | Set-Content c:\Windows\System32\drivers\etc\hosts}
Else{"Nothing happened"}