Powershell scripting - replace text in files - powershell

Given these powershell functions below. I'm trying to replace the version text with another version that I specify. All my paths are correct, and I output both the current version and new version appropriatlely. When i go to replace them, the file indicates that it has changed, but no text was changed.
Alternately, I've found if i specify the exact current version as a string variable and bypass the Get-VersionXXX calls, it replaces the version without issues. Is there anything obvious I'm doing wrong?
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (-Not(Test-Path -path $versionFile))
{
return ""
}
else
{
#foreach($line in [System.IO.File]::ReadLines($versionFile))
(Get-Content -Path $versionFile -Raw) | ForEach-Object {
$line = $_
if ($line.Contains($versionString))
{
if ($line.Contains(""""))
{
$tokens = $line.Split('\"')
return $tokens[1]
}
}
}
}
return ""
}
function Get-VersionOnlyFromFile([string] $versionFile, [string] $versionString)
{
[string] $versionAndDate = Get-VersionAndDateFromFile $versionFile $versionStriong
return VersionFromVersionAndDateString $versionAndDate
}
function Get-VersionFromVersionAndDateString([string] $versionAndDateString)
{
if (!$versionAndDateString)
{
return ""
}
else
{
if ($versionAndDateString.Contains(" "))
{
$newTokens = $versionAndDateString.Trim().Split(" ")
return $newTokens[0]
}
else
{
return $versionAndDateString
}
}
}
function ReplaceTextInFile([string] $fullPath, [string] $oldString, [string] $newString)
{
Write-Host "Old " $oldString
Write-Host "New " $newString
((Get-Content -path $fullPath -Raw) -replace $oldString,$newString) | Set-Content -Path $fullPath
}
Calling code:
[string] $newVersionString = "1.2.3.4.6 09-16-2021"
[string] $currentVersionString = Get-VersionAndDateFromFile $pathToVersionFile "SW_VERSION"
ReplaceTextInFile -fullPath $pathToVersionFile -oldString $currentVersionString -newString $newVersionString
finally: file is a header file called Version.h
#define SW_VERSION "1.2.3.4.5 09-14-2021"

Unless explicitly redirected all output in a function is returned to the pipeline. The return command is redundant. As such, you're sending back two things in your function, first is the version number and second is an empty string (lines starting with >>>):
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (-Not(Test-Path -path $versionFile))
{
return ""
}
else
{
#foreach($line in [System.IO.File]::ReadLines($versionFile))
(Get-Content -Path $versionFile -Raw) | ForEach-Object {
$line = $_
if ($line.Contains($versionString))
{
if ($line.Contains(""""))
{
$tokens = $line.Split('\"')
>>> return $tokens[1]
}
}
}
}
>>> return ""
}
You convert the output to a string, and by all accounts it looks right, but if you look more closely you'll see there's a space after the date:
PS C:\WINDOWS\system32> ">$currentVersionString<"
>1.2.3.4.5 09-14-2021 <
If we comment out the last return in that function the space goes away, and your replace should work fine.
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (-Not(Test-Path -path $versionFile))
{
return ""
}
else
{
#foreach($line in [System.IO.File]::ReadLines($versionFile))
(Get-Content -Path $versionFile -Raw) | ForEach-Object {
$line = $_
if ($line.Contains($versionString))
{
if ($line.Contains(""""))
{
$tokens = $line.Split('\"')
return $tokens[1]
}
}
}
}
# return ""
}
To be honest, it would be much simpler to do this:
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (Test-Path -path $versionFile)
{
Get-Content -Path $versionFile -Raw | Where-Object{$_ -match "$versionString\s+""(.+?)"""} | ForEach-Object {
$Matches[1]
}
}
}

Run your Replace Function with these 2 added lines, as shown below...
$old = $oldString -replace '\s','\s';
$old = $old -replace '\s$';
You'll find you're capturing an extra space at the end of your OldString variable. Like so: 1.2.3.4.5\s\s09-16-2021\s
The two lines fix the issue and it does the replace... or you can fix your Get-Version function to capture the correct string.
function ReplaceTextInFile([string] $fullPath, [string] $oldString, [string] $newString)
{
Write-Host "Old " $oldString
Write-Host "New " $newString
$old = $oldString -replace '\s','\s'; #replaces space with \s
$old = $old -replace '\\s$'; #removes trailing space at end of line
$old = $oldString -replace '\s','\s'; $old = $old -replace '\\s$'
((Get-Content -path $fullPath -Raw) -replace $old,$newString) | Set-Content -Path $fullPath
}

Related

Comparing two text files and output the differences in Powershell

So I'm new to the Powershell scripting world and I'm trying to compare a list of IPs in text file against a database of IP list. If an IP from (file) does not exist in the (database) file put it in a new file, let's call it compared.txt. When I tried to run the script, I didn't get any result. What am I missing here?
$file = Get-Content "C:\Users\zack\Desktop\file.txt"
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
foreach($line1 in $file){
$check = 0
foreach($line2 in $database)
{
if($line1 != $line2)
{
$check = 1
}
else
{
$check = 0
break
}
}
if ($check == 1 )
{
$line2 | Out-File "C:\Users\zack\Desktop\compared.txt"
}
}
There is a problem with your use of PowerShell comparison operators unlike in C#, equality and inequality are -eq and -ne, and since PowerShell is a case insensitive language, there is also -ceq and -cne.
There is also a problem with your code's logic, a simple working version of it would be:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
# iterate each line in `file.txt`
$result = foreach($line1 in Get-Content "C:\Users\zack\Desktop\file.txt") {
# iterate each line in `database.txt`
# this happens on each iteration of the outer loop
$check = foreach($line2 in $database) {
# if this line of `file.txt` is the same as this line of `database.txt`
if($line1 -eq $line2) {
# we don't need to keep checking, output this boolean
$true
# and break the inner loop
break
}
}
# if above condition was NOT true
if(-not $check) {
# output this line, can be `$line1` or `$line2` (same thing here)
$line1
}
}
$result | Set-Content path\to\comparisonresult.txt
However, there are even more simplified ways you could achieve the same results:
Using containment operators:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
$result = foreach($line1 in Get-Content "C:\Users\zack\Desktop\file.txt") {
if($line1 -notin $database) {
$line1
}
}
$result | Set-Content path\to\comparisonresult.txt
Using Where-Object:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
Get-Content "C:\Users\zack\Desktop\file.txt" | Where-Object { $_ -notin $database } |
Set-Content path\to\comparisonresult.txt
Using a HashSet<T> and it's ExceptWith method (Note, this will also get rid of duplicates in your file.txt):
$file = [System.Collections.Generic.HashSet[string]]#(
Get-Content "C:\Users\zack\Desktop\file.txt"
)
$database = [string[]]#(Get-Content "C:\Users\zack\Desktop\database.txt")
$file.ExceptWith($database)
$file | Set-Content path\to\comparisonresult.txt

How to remove or replace a pattern at the beginning of a string with Powershell?

I want to remove the first dash of this string XXXXX-080-YYYYT If my string starts with XXX or YUO or TRRYTY or TRTR, in order to get a string that looks like XXXXX080-YYYYT with Powershell. If I don't remove the dash and I keep XXXXX-080-YYYYT. Regardless of the length of the word, my goal is to remove the first dash. This a 20200925.csv:
function Remove-FirstDashOnMatch{
[cmdletbinding()]
param(
[Parameter(ValueFromPipeline = $true)]
[string[]]
$ElementCsv="$20200925.csv",
[Parameter()]
[string[]]
$Terms = #()
)
begin {
# create the regex patterns from terms
$patterns = $Terms | ForEach-Object {'(^' + $_ + '\S+?)-' }
}
process{
# Import the contents of the 20200925-01.csv and Create ElementCsv object
$ElementCsv=Import-csv $Extract_20200925.csv -Delimiter ';'
#Loop through all the record in the CSV file
$NewModifiedElement= ForEach($Entry in $ElementCsv){
if ($Entry."Script or expected file(s)" -ilike 'technical') {
$Entry.Jobstream=$Entry.Jobstream.trimStart('PCLD')
}else {
# Get the name of jobSet without extension .ksh ou .bat
$Entry.Jobstream=$Entry."Script or expected file(s)"
$pos_last_point = $Entry.Jobstream.LastIndexOf(".")
#Write-Host $pos_last_point
$Entry.Jobstream = $Entry.Jobstream.Substring(0,$pos_last_point).trimStart('P')
$matchObj = $Entry.Jobstream | Select-String -Pattern $patterns
$Entry.Jobstream -replace $matchObj.Pattern, '$1'
}
$Entry
}
# Export 20200925-01.csv in new 20200925-02.csv file
$NewModifiedElement | Export-Csv "20200925-mesurecommand.csv" -NoTypeInformation -Encoding UTF8
}
}
Remove-FirstDashOnMatch # Call the function
How remove the first dash in String?
# Add some data for example
$data = #'
XXXXX-080-YYYY
WEBXX-080-YYYY
XXXXX-080-YYYY
DOCXXX-080-YYYY
DWHSXXX-080-YYYY
CLOTXsdff-080-YYYY
'# -split '\r?\n'
# function to remove first dash if match on term
function Remove-FirstDashOnMatch {
[cmdletbinding()]
param(
[Parameter(ValueFromPipeline = $true)]
[string[]]
$InputObject,
[Parameter()]
[string[]]
$Terms = #('WEBX', 'DWHS', 'COGN', 'CLOT', 'CLAI')
)
begin {
# create the regex patterns from terms
$patterns = $Terms | ForEach-Object {'(^' + $_ + '\S+?)-' }
}
process {
foreach ($input in $InputObject) {
# check if string matches any pattern
if ($matchObj = $input | Select-String -Pattern $patterns) {
# if match, use replace to remove the '-'
$input -replace $matchObj.Pattern, '$1'
}
else {
$input
}
}
}
}
# Use function
$data | Remove-FirstDashOnMatch
Output
XXXXX-080-YYYY
WEBXX080-YYYY
XXXXX-080-YYYY
DOCXXX-080-YYYY
DWHSXXX080-YYYY
CLOTXsdff080-YYYY
Here's my two cents:
$strArray = 'XXXXX-080-YYYYT','WEBXF-080-YYYYT','DWHSG-080-YYYYT','YYYYY-080-YYYYT','CLAIX-080-YYYYT',
'CLOTS-080-YYYYT','XZXZX-080-YYYYT','ABABA-080-YYYYT','COGNI-080-YYYYT','XXZXX-080-YYYYT'
# use -cmatch if the comparison should be Case-Sensitive
$strArray | ForEach-Object {
if ($_ -match '^(WEBX|DWHS|CLAI|CLOT|COGN)') { ($_ -split '-', 2) -join '' }
else { $_ }
}
Output:
XXXXX-080-YYYYT
WEBXF080-YYYYT
DWHSG080-YYYYT
YYYYY-080-YYYYT
CLAIX080-YYYYT
CLOTS080-YYYYT
XZXZX-080-YYYYT
ABABA-080-YYYYT
COGNI080-YYYYT
XXZXX-080-YYYYT
You can use -creplace and a multiline regex. You should also use -Raw for efficiency.
# use `-replace` instead for case insensitive
(Get-Content 'path/to/file.example' -Raw) -creplace '(?m)^(WEBX|DWHS|COGN|CLOT|CLAI)-', '$1'
See https://regex101.com/r/B5Yjvl/1 for regex details.

How to output return value using Powershell?

I want to output the return of this process. Anyone can help me please. Thank you.
$name = $false
switch -regex -file .\bios.txt {
'^Product Name' { $name = $true; continue }
'^\s' { if ($name) { $_.Trim() }}
'^\S' { if ($name) { return } Out-File .\PN.txt}
}
I tried that way, but the output file is empty.
The Out-File .\PN.txt command is only ever reached for (a) lines that start with a non-whitespace character (\S) while (b) $name isn't $true.
When it is reached, it creates an empty .\PN.txt file, due to lack of input.
If, perhaps, your intent was to send all output from the switch statement to a file, try the following:
$name = $false
& {
switch -regex -file .\bios.txt {
'^Product Name' { $name = $true; continue }
'^\s' { if ($name) { $_.Trim() }}
'^\S' { if ($name) { return } $_ }
}
} | Out-File .\PN.txt

Too many if else statements

I am trying to check if a string "03-22-2019" exists (or not and show the result in output) the file is of ".sql" or ".txt" .
execution\development\process.sql
insert.sql
production\logs.txt
restore.sql
rebuild.txt
I am trying below code but I did with too many if else. The above file path stored in the $paths variable. I need to split the path with "\" and get the last part of the path to do something else.
if ($paths -like "*.sql") {
if ($paths.Contains("\")) {
$last = $paths.Split("\")[-1] #get the last part of the path
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
} else {
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
}
} elseIf ($paths -like "*.txt") {
if ($paths.Contains("\")) {
$last = $paths.Split("\")[-1] #get the last part of the path
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
} else {
$result = "03-22-2019" #get this date from some where else
if ($result) {
#check if pattern string exists in that file.
$SEL = Select-String -Path <path location> -Pattern $result
if ($SEL -ne $null) {
Write-Host "`n $last Contains Matching date "
} else {
Write-Host "`n $last Does not Contains date"
}
} else {
Write-Host "`ndate field is blank"
}
}
} else {
Write-Host "other file types"
}
I would make this a little simpler, below is an example to determine if a file contains the date:
$paths = #("c:\path1","c:\path2\subdir")
ForEach ($path in $paths) {
$files = Get-ChildItem -LiteralPath $path -file -include "*.sql","*.txt"
$last = ($path -split "\\")[-1] # contains the last part of the path
$output = ForEach ($file in $files) {
If (Select-String -path $file -pattern "03-22-2019") {
"$($file.fullname) contains the date."
}
else {
"$($file.fullname) does not contain the date."
}
}
}
$output # outputs whether or not a file has the date string
The outer ForEach loop loops through the paths in $paths. Inside of that loop, you can do what you need to each path $path. I used $last to store the last part of the path in the current iteration. You have not said what to do with that.
The inner ForEach checks each .txt and .sql file for the date text 03-22-2019. $output stores a string indicating whether each .txt and .sql file contains the date string.
If your paths contain the file names, then you can use the following alternatives to grab the file name (last part of the path):
$path | split-path -leaf # inside of the outer ForEach loop
# Or
$file.name # inside of the inner ForEach loop
Looks like you should start with a foreach after your $paths variable like this:
foreach ($path in $paths) {
if ($path -like "*.sql") { #Note the use of one single item in the $paths array you have
$last = $path.split("\")[-1]
$result = #Whatever method you want to use to return a DateTime object
if ($result) { #### Now, this line doesn't make any sense. Do you want to compare a date to an older date or something? Maybe something like "if ($result -ge (Get-Date).addDays(-1) )
{ # Do your stuff }
Doing something like:
if ($paths -like "*.sql")
Doesn't work because $paths is an array and you are making a string comparison and never the two shall meet. Now, if you are trying to find if a string is inside a file, you should use something like "Get-Content" or "Import-Csv"
You can use the "Get-Date" cmdlet to get many different formats for the date. Read about that here. If you are trying to compare multiple dates against multiple files, I would start with a for loop on the files like I did up there, and then a for loop on each file for an array of dates. Maybe something like this:
foreach ($path in $paths) {
foreach ($date in $dates) {
# Get the contents of a file and store it in a variable
# Search for the string in that variable and store the results in a variable
# Write to the console
} # End foreach ($date in $dates)
} # End foreach ($path in $paths)
Post some more updated code and let's see what you come up with.

Return from function

I wish to return from the following powershell function if I find a match (for a more complete code sample see my codereview question):
Function Find-Property($fileName, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Foreach-Object {
if($PropertyName -eq $shellfolder.GetDetailsOf($null, $_)){ return $_ }
}
}
This code just appears to return from the scope of the if conditional, which is not so useful.
How can I do this? Do I need a labeled break somewhere?
If you wish to use the return statement to exit the function you can use the foreach keyword instead of the ForEach-Object cmdlet. Here's a demo:
function Foo {
foreach ($number in (0..287)) {
$number # Just show our current iteration.
if ($number -eq 50) {
return $number
}
}
}
No need for a label.
function Find-Property($Filename, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Where {$PropertyName -eq $shellfolder.GetDetailsOf($null, $_)} |
Foreach {$_;break}
}
Another option is to minorly tweak your original function:
function Find-Property($fileName, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Foreach-Object {
if($PropertyName -eq $shellfolder.GetDetailsOf($null, $_)) {$_; break}
}
}