Trying to modify to different files and getting two different outcomes. The successful file "autoexec.cfg works fine. Here is the file contents and powershell code.
c:\temp\autoexec.cfg contains:
disable_write_track = true
webgui_port = 8470
powershell code to modify file:
$WGP = get-random -minimum 8000 -maximum 8999
$line = Get-Content "C:\temp\autoexec.cfg" | Select-String webgui_port | Select-Object -ExpandProperty Line
$content = Get-Content "C:\temp\autoexec.cfg"
$content | ForEach-Object {$_ -replace $line,"webgui_port = $WGP"} | Set-Content "C:\temp\autoexec.cfg"
The file that fails sets up like this.
c:\temp\serverSettings.lua contains:
cfg =
{
["port"] = 10302,
} -- end of cfg
powershell code to modify file
$DCSP = get-random -minimum 10000 -maximum 19999
$line = Get-Content "C:\temp\serverSettings.lua" | Select-String port | Select-Object -ExpandProperty Line
$content = Get-Content "C:\temp\serverSettings.lua"
$content | ForEach-Object {$_ -replace $line," [\`"port\`"] = $DCSP,"} | Set-Content "C:\temp\serverSettings.lua"
The file does not change except it does. I have the file open in Notepadd++ and after running the code Notepad++ sees the file has been changed and wants to reload but there are no changes.
-replace is a regex operator, and [] is a special construct in a regular expression and needs to be escaped properly.
The easiest way to do that is with [regex]::Escape():
$content | ForEach-Object {$_ -replace [regex]::Escape($line)," [\`"port\`"] = $DCSP,"} | Set-Content "C:\temp\serverSettings.lua"
I am new programming, I am trying to improve the following code of a script, I was thinking of making a function to improve it but I do not know where to start or if it is the best option.
$replaceText01 = (Get-Content -path $copyFileLocation -Raw) -replace '"INSTANCENAME="TEST""',$NUEVAINESTANCIA
Set-Content $copyFileLocation $replaceText01
$replaceText02 = (Get-Content -path $copyFileLocation -Raw) -replace '"INSTANCEID="TEST""',$NUEVAINESTANCIAID
Set-Content $copyFileLocation $replaceText02
$replaceText03 = (Get-Content -path $copyFileLocation -Raw) -replace "NT Service\SQLAgent#TEST", $CUENTAAGTN
Set-Content $copyFileLocation $replaceText03
$replaceText04 = (Get-Content -path $copyFileLocation -Raw) -replace "NT Service\MSSQL#TEST", $CUENTASQLSER
Set-Content $copyFileLocation $replaceText04
$user = "$env:UserDomain\$env:USERNAME"
write-host $user
$replaceText = (Get-Content -path $copyFileLocation -Raw) -replace "##MyUser##", $user
Set-Content $copyFileLocation $replaceText
First off, I would probably try to read the file only once. Then since you are doing many similar operations, I would put all the data about the operations in an array, and then iterate over those data.
In this code, I first read the file. Then I define all the strings that should be replaced together with the strings that should replace them. Then I use a loop to iterate over the data so that we don't repeat the same code all the time.
$data = Get-Content -Path $copyFileLocation -Raw
$replacements = #(
#('"INSTANCENAME="TEST""', $NUEVAINESTANCIA),
#('"INSTANCEID="TEST""', $NUEVAINESTANCIAID),
#("NT Service\SQLAgent#TEST", $CUENTAAGTN),
#("NT Service\MSSQL#TEST", $CUENTASQLSER),
#("##MyUser##", "$env:UserDomain\$env:USERNAME")
)
$replacements | ForEach-Object {
$data = $data.Replace($_[0], $_[1])
}
Set-Content -Path $copyFileLocation -Value $data
It's also possible to get this even shorter if you use the pipeline instead of assigning the data to a variable
$data = Get-Content -Path $copyFileLocation -Raw
#(
#('"INSTANCENAME="TEST""', $NUEVAINESTANCIA),
#('"INSTANCEID="TEST""', $NUEVAINESTANCIAID),
#("NT Service\SQLAgent#TEST", $CUENTAAGTN),
#("NT Service\MSSQL#TEST", $CUENTASQLSER),
#("##MyUser##", "$env:UserDomain\$env:USERNAME")
) | ForEach-Object {
$data = $data.Replace($_[0], $_[1])
}
Set-Content -Path $copyFileLocation -Value $data
Edit: Missed that you were asking on how to make it into a function.
By looking at what your doing I assume you are modifying an SQL unattended install file, and have named it as such.
A good idea here is to make most of the parameters mandatory, so you are sure the user at least specifies all the required parameters. Maybe you want to specify MyUser later, so this is a good candidate for being a parameter with a default value.
Function Set-SQLInstallFileVariables {
Param(
[Parameter(Mandatory)][string]$FilePath,
[Parameter(Mandatory)][string]$NUEVAINESTANCIA,
[Parameter(Mandatory)][string]$NUEVAINESTANCIAID,
[Parameter(Mandatory)][string]$CUENTAAGTN,
[Parameter(Mandatory)][string]$CUENTASQLSER,
[string]$MyUser = "$env:UserDomain\$env:USERNAME"
)
$data = Get-Content -Path $FilePath -Raw
#(
#('"INSTANCENAME="TEST""', $NUEVAINESTANCIA),
#('"INSTANCEID="TEST""', $NUEVAINESTANCIAID),
#("NT Service\SQLAgent#TEST", $CUENTAAGTN),
#("NT Service\MSSQL#TEST", $CUENTASQLSER),
#("##MyUser##", $MyUser)
) | ForEach-Object {
$data = $data.Replace($_[0], $_[1])
}
Set-Content -Path $copyFileLocation -Value $data
}
The first thing to notice is that you read and write the file over and over again on every replacement, which is not very efficient.
This is not needed; once read the content is in a string variable and can get manipulated in memory multiple times before writing back to file.
One approach is to use string arrays that hold the search and replacement strings.
For this to work properly, both arrays must have the same number of elements.
$inputFile = 'D:\Test\TheFile.txt' # your input file path here ($copyFileLocation)
$outputFile = 'D:\Test\TheReplacedFile.txt' # for safety create a new file instead of overwriting the original
$searchStrings = '"INSTANCENAME="TEST""','"INSTANCEID="TEST""',"NT Service\SQLAgent#TEST","NT Service\MSSQL#TEST","##MyUser##"
$replaceStrings = $NUEVAINESTANCIA, $NUEVAINESTANCIAID, $CUENTAAGTN, $CUENTASQLSER, "$env:UserDomain\$env:USERNAME"
# get the current content of the file
$content = Get-Content -path $copyFileLocation -Raw
# loop over the search and replace strings to do all replacements
for ($i = 0; $i -lt $searchStrings.Count; $i++) {
$content = $content -replace [regex]::Escape($searchStrings[$i]), $replaceString[$i]
}
# finally, write the updated content to a (new) file
$content | Set-Content -Path $copyFileLocation
Another approach would be to use a Hashtable that stores both the search strings and the replacement strings:
$inputFile = 'D:\Test\TheFile.txt' # your input file path here ($copyFileLocation)
$outputFile = 'D:\Test\TheReplacedFile.txt' # for safety create a new file instead of overwriting the original
$hash = #{
'"INSTANCENAME="TEST""' = $NUEVAINESTANCIA
'"INSTANCEID="TEST""' = $NUEVAINESTANCIAID
"NT Service\SQLAgent#TEST" = $CUENTAAGTN
"NT Service\MSSQL#TEST" = $CUENTASQLSER
"##MyUser##" = "$env:UserDomain\$env:USERNAME"
}
# get the current content of the file
$content = Get-Content -path $copyFileLocation -Raw
# loop over the items in the hashtable to do all replacements
$hash.GetEnumerator() | ForEach-Object {
# the `$_` is an automatic variable you get within a ForEach-Object{}
# It represents a single item on each iteration.
$content = $content -replace [regex]::Escape($_.Key), $_.Value
}
# finally, write the updated content to a (new) file
$content | Set-Content -Path $copyFileLocation
In both cases, we're using -replace, which is a case-insensitive regex replacement. Because your search strings hold characters that have special meaning in regex (# and \) we need to escape these with [regex]::Escape()
Hope that helps
Here is mine attempt at it. Its not meant to be lean. Rather to be really clear about what it is doing while being full of functions (too many) and stopping you from getting the content multiple times.
After seeing what everyone else answered, their's is better code. Hopefully mine is readable and give you a better idea about using the pipeline and functions :)
Function Replace1 {Process{$_ -replace '"INSTANCENAME="TEST""',$NUEVAINESTANCIA}}
Function Replace2 {Process{$_ -replace '"INSTANCEID="TEST""',$NUEVAINESTANCIAID}}
Function Replace3 {Process{$_ -replace "NT Service\SQLAgent#TEST", $CUENTAAGTN}}
Function Replace4 {Process{$_ -replace "NT Service\MSSQL#TEST", $CUENTASQLSER}}
Function Replace6 {Process{$_ -replace "##MyUser##", $user}}
$user = "$env:UserDomain\$env:USERNAME"
Write-Host $user
Get-Content -path $copyFileLocation | Replace1 | Replace2 | Replace3 | Replace4 | Replace5 | Replace6 | Set-Content -path $copyFileLocation
I am using the following script that iterates through hundreds of text files looking for specific instances of the regex expression within. I need to add a second data point to the array, which tells me the object the pattern matched in.
In the below script the [Regex]::Matches($str, $Pattern) | % { $_.Value } piece returns multiple rows per file, which cannot be easily output to a file.
What I would like to know is, how would I output a 2 column CSV file, one column with the file name (which should be $_.FullName), and one column with the regex results? The code of where I am at now is below.
$FolderPath = "C:\Test"
$Pattern = "(?i)(?<=\b^test\b)\s+(\w+)\S+"
$Lines = #()
Get-ChildItem -Recurse $FolderPath -File | ForEach-Object {
$_.FullName
$str = Get-Content $_.FullName
$Lines += [Regex]::Matches($str, $Pattern) |
% { $_.Value } |
Sort-Object |
Get-Unique
}
$Lines = $Lines.Trim().ToUpper() -replace '[\r\n]+', ' ' -replace ";", '' |
Sort-Object |
Get-Unique # Cleaning up data in array
I can think of two ways but the simplest way is to use a hashtable (dict). Another way is create psobjects to fill your Lines variable. I am going to go with the simple way so you can only use one variable, the hashtable.
$FolderPath = "C:\Test"
$Pattern = "(?i)(?<=\b^test\b)\s+(\w+)\S+"
$Results =#{}
Get-ChildItem -Recurse $FolderPath -File |
ForEach-Object {
$str = Get-Content $_.FullName
$Line = [regex]::matches($str,$Pattern) | % { $_.Value } | Sort-Object | Get-Unique
$Line = $Line.Trim().ToUpper() -Replace '[\r\n]+', ' ' -Replace ";",'' | Sort-Object | Get-Unique # Cleaning up data in array
$Results[$_.FullName] = $Line
}
$Results.GetEnumerator() | Select #{L="Folder";E={$_.Key}}, #{L="Matches";E={$_.Value}} | Export-Csv -NoType -Path <Path to save CSV>
Your results will be in $Results. $Result.keys contain the folder names. $Results.Values has the results from expression. You can reference the results of a particular folder by its key $Results["Folder path"]. of course it will error if the key does not exist.
I can do this one-by-one with bookmarking and other Notepad++ features, but I will be doing this frequently to edit documents. I have used the below powershell for removing all lines except those containing a certain string, but how would I do it for, say 50 strings.
$SourceFile = 'C:\PATH\TO\FILE.csv'
$Pattern = 'word||'
(Get-Content $SourceFile) | % {if ($_ -match $Pattern){$_}} | Set-Content $SourceFile
I guess $Match should be $Pattern in your example.
You can specify multiple keywords in your pattern, like this:
$SourceFile = 'C:\PATH\TO\FILE.csv'
$Pattern = 'word|excel|powerpoint'
(Get-Content $SourceFile) | Where-Object { $_ -match $Pattern } | Set-Content $SourceFile
I have a text file containing some data as follows:
test|wdthe$muce
check|muce6um#%
How can I check for a particular string like test and retrieve the text after the | symbol to a variable in a PowerShell script?
And also,
If Suppose there is variable $from=test#abc.com and how to search the file by splitting the text before "#" ?
this may be one possible solution
$filecontents = #'
test|wdthe$muce
check|muce6um#%
'#.split("`n")
# instead of the above, you would use this with the path of the file
# $filecontents = get-content 'c:\temp\file.txt'
$hash = #{}
$filecontents | ? {$_ -notmatch '^(?:\s+)?$'} | % {
$split = $_.Split('|')
$hash.Add($split[0], $split[1])
}
$result = [pscustomobject]$hash
$result
# and to get just what is inside 'test'
$result.test
*note: this may only work if there is only one of each line in the file. if you get an error, try this other method
$search = 'test'
$filecontents | ? {$_ -match "^$search\|"} | % {
$_.split('|')[1]
}
First you need to read the text from the file.
$content = Get-Content "c:\temp\myfile.txt"
Then you want to grab the post-pipe portion of each matching line.
$postPipePortion = $content | Foreach-Object {$_.Substring($_.IndexOf("|") + 1)}
And because it's PowerShell you could also daisy-chain it together instead of using variables:
Get-Content "C:\temp\myfile.txt" | Foreach-Object {$_.Substring($_.IndexOf("|") + 1)}
The above assumes that you happen to know every line will include a | character. If this is not the case, you need to select out only the lines that do have the character, like this:
Get-Content "C:\temp\myfile.txt" | Select-String "|" | Foreach-Object {$_.Line.Substring($_.Line.IndexOf("|") + 1)}
(You need to use the $_.Line instead of just $_ now because Select-String returns MatchInfo objects rather than strings.)
Hope that helps. Good luck.
gc input.txt |? {$_ -match '^test'} |% { $_.split('|') | select -Index 1 }
or
sls '^test' -Path input.txt |% { $_.Line.Split('|') | select -Index 1 }
or
sls '^test' input.txt |% { $_ -split '\|' | select -Ind 1 }
or
(gc input.txt).Where{$_ -match '^test'} -replace '.*\|'
or
# Borrowing #Anthony Stringer's answer shape, but different
# code, and guessing names for what you're doing:
$users = #{}
Get-Content .\input.txt | ForEach {
if ($_ -match "(?<user>.*)\|(?<pwd>.*)") {
$users[$matches.user]=$matches.pwd
}
}
$users = [pscustomobject]$users