Set-content keeping busy my files - powershell

I'm trying to replace multiple strings for new ones, always in the same file.
This would be an example. This give me no problems.
(get-content modTags.bas) | %{$_ -replace "rng_origin.Offset(ColumnOffset:=1)", "rng_origin.Offset(ColumnOffset:=0)"} | set-content modTags.bas
But if I repeat this line in the script (in fact, i must do it like 20 times) I get the error that the file is currently in use.
I have tried to put (set-content) like in (get-content), but it seems it doesn't works for only allow parameters in the first command in the pipeline.
I already know how to "bypass" this error.
By typing all my replacements inline it works (or continue the code in a new line) like this.
(get-content modTags.bas) | %{$_ -replace "X","Y" `
-replace "A","B"} | set-content modTags.bas
So this is a question about why set-content keeps the file occupy for new query in the same script and how can it be avoided? With get-content it easy with the () solution, and I was kinda expecting something similar for set-content.
And second. Could you suggest any better alternative for replacing multiple strings for different ones and save it in the same file (not creating a file.new.txt file.old.txt or something like that)
Thanks!

Related

Batch File to Find and Replace in text file using whole word only?

I am writing a script which at one point has to check in a text file and remove certain strings. So far I have this:
powershell -Command "(gc myFile.txt) -replace 'foo', 'bar' | Out-File -encoding ASCII myFile.txt"
The only problem is that that can find and replace but will not remove the line all together.
The second problem is that say I am removing the line that has Mark, it needs to not remove a line that has something like Markus.
I don't know if this is possible with the powershell interface?
Your current code will only replace foo with bar, this is what replace does.
Removing the whole line if it matches requires a different approach, almost backwards, as you can use notmatch to output any lines that do not match you filter - effectively removing them.
Also using regex word boundaries will then only match Mark but not Markus:
(Get-Content file.txt) | Where-Object {$_ -notmatch "\bMark\b"} | Set-Content file.txt

Pipes in replace causing line to be duplicated

I have a script that I need to replace a couple of lines in. The first replace is going fine but the second is wiping out my file and duplicating the line multiple times.
My code
(get-content $($sr)) -replace 'remoteapplicationname:s:SHAREDAPP',"remoteapplicationcmdline:s:$($sa)" | Out-File $($sr)
(get-content $($sr)) -replace 'remoteapplicationprogram:s:||SHAREDAPP',"remoteapplicationprogram:s:||$($sa)" | Out-File $($sr)
The first replace works perfectly. The second one is causing this:
remoteapplicationprogram:s:||stagaredrremoteapplicationprogram:s:||stagarederemoteapplicationprogram:s:||stagareddremoteapplicationprogram:s:||stagarediremoteapplicationprogram:s:||stagaredrremoteapplicationprogram:s:||stagarederemoteapplicationprogram:s:||stagaredcremoteapplicationprogram:s:||stagaredtremoteapplicationprogram:s:||stagaredcremoteapplicationprogram:s:||stagaredlremoteapplicationprogram:s:||stagarediremoteapplicationprogram:s:||stagaredpremoteapplicationprogram:s:||stagaredbremoteapplicationprogram:s:||stagaredoremoteapplicationprogram:s:||stagaredaremoteapplicationprogram:s:||stagaredrremoteapplicationprogram:s:||stagareddremoteapplicationprogram:s:||stagared:remoteapplicationprogram:s:||stagarediremoteapplicationprogram:s:||stagared:remoteapplicationprogram:s:||stagared1remoteapplicationprogram:s:||stagared
etc...
Is this because of the ||? If so, how do I get around it?
Thanks!
To begin with, you should be using slightly more meaningful names for your variables. Especially if you want someone else to be reviewing your code.
The gist of your issue is that -replace supports regexes (regular expressions), and you have regex control characters in your pattern string. Consider the following simple example, and notice everywhere the replacement string is found:
PS C:\Users\Matt> "ABCD" -replace "||", "bagel"
bagelAbagelBbagelCbagelDbagel
-replace is also an array operator, so it works on every line of the input file, which is nice. For simplicity's sake, if you are not using a regex, you should just consider using the string method .Replace(), but it is case-sensitive, so that might not be ideal. So let's escape those control characters in the easiest way possible:
$patternOne = [regex]::Escape('remoteapplicationname:s:SHAREDAPP')
$patternTwo = [regex]::Escape('remoteapplicationprogram:s:||SHAREDAPP')
(get-content $sr) -replace $patternOne, "remoteapplicationcmdline:s:$sa" | Out-File $($sr)
(get-content $sr) -replace $patternTwo, "remoteapplicationprogram:s:||$sa" | Out-File $($sr)
Now we get both patterns matched as you have them written. Run $patternTwo on the console to see what has changed to it! $patternOne, as written, has no regex control characters in it, but it does not hurt to use the escape method if you are just expecting simple matching.
Aside from the main issue pointed out, there is also some redundancy and misconception that can be addressed here. I presume you are updating a source file to replace all occurrences of those strings, yes? Well, you don't need to read the file in twice, given that you can chain -replace:
$patternOne = [regex]::Escape('remoteapplicationname:s:SHAREDAPP')
$patternTwo = [regex]::Escape('remoteapplicationprogram:s:||SHAREDAPP')
(get-content $sr) -replace $patternOne, "remoteapplicationcmdline:s:$sa" -replace $patternTwo, "remoteapplicationprogram:s:||$sa" |
Set-Content $sr
Perhaps that will do what you intended.
You might notice that I've removed the subexpressions operators ($(...)) around your variables. While they have their place, they don't need to be used here. They are only needed inside more complicated strings, like when you need to expand object properties or something.

Replacing contents of a text file using PowerShell

I've looked all around this site and can't quite seem to find anything that fits my situation. Basically, I am trying to write an addition to the NETLOGON file that will replace text in a text file on all of our users' desktops. The current text is static across the board.
The text I want it changed to will be unique to each user. I want to change the current text (user1) to the users AD username (i.e. johnd, janed, etc.). I am using Windows Server 2008 R2 and all the workstations are Windows 7 Professional SP1 64 bit.
Here's what I have tried so far (with a few variables, which none have worked for one reason or the other):
gc c:\Users\%USERNAME%\desktop\VPN.txt' -replace "user1",$env:username | out-file c:\Users\%USERNAME%\desktop\VPN.txt
I didn't get an error, but it also did not go back to the normal "PS C:>" prompt, just ">>>" and the file did not change as anticipated.
If that is how you have the code exactly then I suppose it is because you have an opening single quote without a closing one. You are still going to have two other problems and you have one answer in your code. The >>> is the line continuation characters because the parser knows that the code is not complete and giving you the option to continue with the code. If you were purposely coding a single line on multiple lines you would consider this a feature.
$path = "c:\Users\$($env:username)\desktop\VPN.txt"
(Get-Content $path) -replace "user1",$env:username | out-file $path
Closed the path in quotes and used a variable since you called the path twice.
%name% is used in command prompt. Environment variables in PowerShell use the $env: provider which you did you once in your snippet.
-replace is a regex replaced tool that can work against Get-Content but you need to capture the result in a sub expression first.
Secondly with -replace is for regex and your string is not regex based you could just use .Replace() as well.
Set-Content is generally preferred over Out-File for performance reasons.
All that being said...
you could also try something like this.
$path = "c:\Users\$($env:username)\desktop\VPN.txt"
(Get-Content $path).Replace("user1",$env:username) | Set-Content $path
Do you want to only replace the first occurrence?
You could use a little regex here with a tweak in how you get the use Get-Content
$path = "c:\Users\$($env:username)\desktop\VPN.txt"
(Get-Content $path | Out-String) -replace "(.*?)user1(.*)",('$1{0}$2' -f $env:username) | out-file $path
Regex will match the entire file. There are two groups which it captures.
(.*?) - Up until the first "user1"
(.*) - Everything after that
Then we use the format operator to sandwich the new username in between those capture groups.
Use:
(Get-Content $fileName) | % {
if ($_.ReadCount -eq 1) {
$_ -replace "$original", "$content"
}
else {
$_
}
} | Set-Content $fileName

Powershell script write back to sources from drag and drop

I need to create a powershell script that removes quotes from CSV files in a user friendly drag and drop way. I have the basics of the script down courtesy of this page:
http://blogs.technet.com/b/heyscriptingguy/archive/2011/11/02/remove-unwanted-quotation-marks-from-csv-files-by-using-powershell.aspx
And I've already sucessfully made .ps1 files drag and droppable courtesy of this stack overflow question:
Drag and Drop to a Powershell script
The author of the answer implies that it's just as easy to drop a single file, many files, and folders with lots of files in them. However, I have yet to figure this out in a way that can also can write back to the source file. Here's my current code:
Param([string[]]$file)
(gc $file) | % {$_ -replace '"', ""} | out-file C:\Users\pfoster\Desktop\Output\test.txt -Fo -En ascii
Currently, this will only accept a single file, and output the result as a txt to a specified file regardless of the source file type (I can change that to CSV easily but I'd like the script to mirror the source). Ideally, I'd like it to accept files and folders, and to rewrite the source file. I have a feeling this would involve the get-ChildItem but I'm not sure how to implement that in the current scenario. I've also tried out-file $file and that didn't work either.
Thanks for the help!
For writing the modified content back to the original files try something like this:
foreach ($file in $ARGS) {
(Get-Content $file) -replace '"', '' | Out-File $file -Encoding ASCII -Force
}
Use a foreach in loop, because you need the file name in more than one place in the pipeline. Reading the content in a subshell and then piping the modified content into the Out-File cmdlet makes sure that the output file is only written after the content was already read.
Don't use a redirection operator ((Get-Content $file) >$file), because that would first open the file for writing (effectively truncating it) and afterwards read the content from the now empty file.
Beware that this approach may cause problems with large files, because each file is read completely into the RAM before they're processed and written back to disk. If a file doesn't fit into the available RAM the computer will start swapping, thus causing significant performance degradation.

Find and Replace in a Large File

I want to find a piece of text in a large xml file and want to replace with some other text. The size of the file is around (50GB). I want to do this in command line. I am looking at PowerShell and want to know if it can handle the large size.
Currently I am trying something like this but it does not like it
Get-Content C:\File1.xml | Foreach-Object {$_ -replace "xmlns:xsi=\"http:\/\/www\.w3\.org\/2001\/XMLSchema-instance\"", ""} | Set-Content C:\File1.xml
The text I want to replace is xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" with an empty string "".
Questions
Can PowerShell handle large
files
I don't want the replace to happen in
memory and prefer streaming assuming
that will not bring the server to
its knees.
Are there any other approaches I can take (different
tools/strategy?)
Thanks
I had a similar need (and similar lack of powershell experience) but cobbled together a complete answer from the other answers on this page plus a bit more research.
I also wanted to avoid the regex processing, since I didn't need it either -- just a simple string replace -- but on a large file, so I didn't want it loaded into memory.
Here's the command I used (adding linebreaks for readability):
Get-Content sourcefile.txt
| Foreach-Object {$_.Replace('http://example.com', 'http://another.example.com')}
| Set-Content result.txt
Worked perfectly! Never sucked up much memory (it very obviously didn't load the whole file into memory), and just chugged along for a few minutes then finished.
Aside from worrying about reading the file in chunks to avoid loading it into memory, you need to dump to disk often enough that you aren't storing the entire contents of the resulting file in memory.
Get-Content sourcefile.txt -ReadCount 10000 |
Foreach-Object {
$line = $_.Replace('http://example.com', 'http://another.example.com')
Add-Content -Path result.txt -Value $line
}
The -ReadCount <number> sets the number of lines to read at a time. Then the ForEach-Object writes each line as it is read. For a 30GB file filled with SQL Inserts, I topped out around 200MB of memory and 8% CPU. While, piping it all into Set-Content at hit 3GB of memory before I killed it.
It does not like it because you can't read from a file and write back to it at the same time using Get-Content/Set-Content. I recommend using a temp file and then at the end, rename file1.xml to file1.xml.bak and rename the temp file to file1.xml.
Yes as long as you don't try to load the whole file at once. Line-by-line will work but is going to be a bit slow. Use the -ReadCount parameter and set it to 1000 to improve performance.
Which command line? PowerShell? If so then you can invoke your script like so .\myscript.ps1 and if it takes parameters then c:\users\joe\myscript.ps1 c:\temp\file1.xml.
In general for regexes I would use single quotes if you don't need to reference PowerShell variables. Then you only need to worry about regex escaping and not PowerShell escaping as well. If you need to use double-quotes then the back-tick character is the escape char in double-quotes e.g. "`$p1 is set to $ps1". In your example single quoting simplifies your regex to (note: forward slashes aren't metacharacters in regex):
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
Absolutely you want to stream this since 50GB won't fit into memory. However, this poses an issue if you process line-by-line. What if the text you want to replace is split across multiple lines?
If you don't have the split line issue then I think PowerShell can handle this.
This is my take on it, building on some of the other answers here:
Function ReplaceTextIn-File{
Param(
$infile,
$outfile,
$find,
$replace
)
if( -Not $outfile)
{
$outfile = $infile
}
$temp_out_file = "$outfile.temp"
Get-Content $infile | Foreach-Object {$_.Replace($find, $replace)} | Set-Content $temp_out_file
if( Test-Path $outfile)
{
Remove-Item $outfile
}
Move-Item $temp_out_file $outfile
}
And called like so:
ReplaceTextIn-File -infile "c:\input.txt" -find 'http://example.com' -replace 'http://another.example.com'
The escape character in powershell strings is the backtick ( ` ), not backslash ( \ ). I'd give an example, but the backtick is also used by the wiki markup. :(
The only thing you should have to escape is the quotes - the periods and such should be fine without.