Replace next two lines after string match - regex

I´m currently working on a script that should based on user´s choice replace two lines in a file after a matching string.
The file I want to edit looks like this:
[default]
string_a=sadasdasdas
string_b=dasdasdasdas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
I want always to override string_a & string_b after [default].
Note that [default] could also be at the very bottom of the file, therefore I cannot just count lines an do it that static.
The user can pick between (in this case) profile 1 & profile 2. After he picked e.g profile 2, string_a & string_b of profile2 should be replaced with string_a & string_b of default.
My current code like like this:
$filePath = './credentials'
$fileContent = Get-Content $filePath
$profiles = [regex]::Matches($fileContent, '\[(.*?)\]') |ForEach-Object { $_.Groups[1].Value }
Write-Host "Following profiles found: "
for ($i=0; $i -lt $profiles.length; $i++){
Write-Host $i"." $profiles[$i]
}
$userInput = Read-Host "Which profile set to default? "
Write-Host $profiles[$userInput]
$fileContent | Select-String $profiles[$userInput] -Context 1,2 | ForEach-Object {
$stringA = $_.Context.PostContext[0]
$stringB = $_.Context.PostContext[1]
#At this point I have access to the both string´s I want to replace the string´s of the default profile
# I could do this, but then I still have the old lines in the file...
# So the following code is not an option.
$NewContent = Get-Content -Path $filePath |
ForEach-Object {
# Output the existing line to pipeline in any case
$_
# If line matches regex
if($_ -match ('^' + [regex]::Escape('[default]')))
{
# Add output additional line
$stringA
$stringB
}
}
# Write content of $NewContent varibale back to file
$NewContent | Out-File -FilePath $filePath -Encoding Default -Force
}
Example output file, in case the user picked profile1 as the new default
[default]
string_a=xxxxxx
string_b=xsaassaasas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
Hope this is not obvious, but as it is my first real powershell script I was not able to find a solution for my problem yet.
Any help would be great!
Thanks

Example:
# This is sample data
$lines = #(#'
[default]
string_a=sadasdasdas
string_b=dasdasdasdas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
'# -split "`r`n")
# In real world use:
# $encoding = [System.Text.Encoding]::ASCII
# $lines = [System.IO.File]::ReadAllLines($path, $encoding)
#Read file
$lines = $lines | ForEach-Object { $_.Trim()} # Trim spaces
$sections = #{}
$currentSection = $null
$hasErrors = $false
$lines | ForEach-Object {
if ([String]::IsNullOrWhiteSpace($_)) {
#ignore
} elseif ($_.StartsWith('[') -and $_.EndsWith(']') ) {
$currentSection = $_.Substring($_.IndexOf('[') + 1, $_.LastIndexOf(']') - 1)
$sections[$currentSection] = #{}
} elseif ($sections.ContainsKey($currentSection)) {
$PVPair = [String[]]$_.Split('=',2)
if ($PVPair.Count -eq 2) {
$sections[$currentSection][$PVPair[0]] = $PVPair[1]
} else {
Write-Warning -Message "Wrong line format [$($_)]"
$hasErrors = $true
}
} else {
Write-Warning -Message "Unexpected behaviour on section $currentSection, line $($_)"
$hasErrors = $true
}
}
if ($hasErrors) {
Write-Error -Message 'Errors occured'
return
}
# Choice
$choice = $null
$choiceVariants = #($sections.Keys | Where-Object { $_ -ne 'default' })
while ($choiceVariants -notcontains $choice) {
Write-Host "Choose between $($choiceVariants -join ',')"
$choice = $choiceVariants | Out-GridView -Title 'Choose variant' -OutputMode Single
#Alternative: $choice = Read-Host -Prompt "Your choice"
}
Write-Host -ForegroundColor Yellow "You choose $($choice)"
# Change
$sections[$choice]['string_a'] = $sections['default']['string_a']
$sections[$choice]['string_b'] = 'newXSAA'
# Output
$outputLines = $sections.Keys | ForEach-Object {
$sectionName = $_
Write-Output "[$sectionName]"
$sections[$sectionName].Keys | ForEach-Object {
Write-Output "$_=$($sections[$sectionName][$_])"
}
}
# This is sample output
$outputLines | % { Write-Host $_ -f Magenta }
# In Real world:
# [System.IO.File]::WriteAllLines($path, $outputLines, $encoding)

Related

Powershell - Searching for strings (in list) in a word document

I found some code for searching for strings in a Word Document. I altered it to suit my needs (I need to search from a very long list of strings). Unfortunately, I am getting a weird error.
While the script is running, it opens the word document, searches the word document and here is where it gets weird, instead of closing the document and opening the next, it presents me with a 'save as' dialog box and the script hangs until I cancel out of it. When I cancel out of it, my script continues.
Here is the script I'm using, would anyone see where I'm going south?
$results = #{}
Write-Host "Loading getStringMatch into memory" -ForegroundColor DarkMagenta
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
Write-Host "Searching In ... $($File.FullName) " -ForegroundColor DarkYellow
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
If($range.Text -match ".{$($charactersAround)}$($findtext).{$($charactersAround)}"){
$properties = #{
File = $file.FullName
Match = $findtext
TextAround = $Matches[0]
}
$results += #(New-Object -TypeName PsCustomObject -Property $properties)
}
$document.close()
Write-Host "Closing Document ... $($File.FullName) " -ForegroundColor Red
}
#If($results){
# $results | Export-Csv $output -NoTypeInformation
#}
$application.quit()
}
$searchWords=Get-Content "C:\Temp\USDA_Search_For.txt"
Foreach ($sw in $searchWords)
{
Write-Host "Setting Variables ..." -ForegroundColor DarkMagenta
Set-StrictMode -Version latest
$path = "C:\Temp"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "C:\Temp\Found.csv"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First"
$charactersAround = 30
#$results = #{}
$findtext = $sw
Write-Host "Searching For ... $findtext" -ForegroundColor Green
getStringMatch
#clean up stuff
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
If($results){
$results | Export-Csv $output -NoTypeInformation
}
import-csv $output

Regular expression in power shell to convert character between double quotes to upper case

I want to write a powershell script which will convert a string which is present between double quotes in a file, and convert it into upper case.
The files are placed in different folders.
I am able to extract the string between the double quotes and convert it to upper case, but not able to replace it in the correct position.
Ex : This is the input string.
"e" //&&'i&&
The output should be
"E" //&&'i&&
This is what i have tried. Also this even i not replacing the content of the file.
$items = Get-ChildItem * -recurse
# enumerate the items array
foreach ($item in $items)
{
# if the item is a directory, then process it.
if ($item.Attributes -ne "Directory")
{
(Get-Content $item.FullName ) |
Foreach-Object {
if (($_ -match '\"'))
{
$str = $_
$ext = [regex]::Matches($str, '".*?"').Value -replace '"'
$ext = $ext.ToUpper()
Write-Host $ext
$_ = $ext
}
else { }
} |
Set-Content $item.FullName
}
}
This can do it. Really I wasn't following your code so I stripped it and modified the regex.
$items = Get-ChildItem "C:\Users\UsernameHere\Desktop\Folder123\*.txt"
# enumerate the items array
foreach ($item in $items){
# if the item is a directory, then process it.
if ($item.Attributes -ne "Directory"){
$content = (gc $item.FullName )
$content = $content.replace('"\w.*"',$matches[0].ToUpper)
$content | sc $item
}
}
If you had powershell 6 or 7:
'"hi"' -replace '".*"', { $_.value.toupper() }
"HI"
'"e" //&&''i&&' -replace '".*"', { $_.value.toupper() }
"E" //&&'i&&
I am able to print the upper case characters with the below code, but the file is not getting updated. It still has the old characters, How to update the fie with new contents.
$items = Get-ChildItem *.txt -recurse
# enumerate the items array
foreach ($item in $items)
{
# if the item is a directory, then process it.
if ($item.Attributes -ne "Directory")
{
(Get-Content $item.FullName ) |
Foreach-Object {
$str = $_
$_ = [regex]::Replace($_, '"[^"]*"', { param($m) $m.Value.ToUpper() })
Write-Host $_
} |
Set-Content $item.FullName
}
}

How to stream text with powershell and regex match on multiline

I have a text file that an application constantly errors to. I want to monitor this file with Powershell and log every error to another source.
Problem to solve: how do i pass multiline text when we are in -wait? Get-Content is passing arrays of strings.
$File = 'C:\Windows\Temp\test.txt'
$content = Get-Content -Path $file
# get stream of text
Get-Content $file -wait -Tail 0 | ForEach-Object {
if ($_ -match '(<ACVS_T>)((.|\n)*)(<\/ACVS_T>)+'){
write-host 'match found!'
}
}
Example of text junks that get drop:
<ACVS_T>
<ACVS_D>03/01/2017 17:24:03.602</ACVS_D>
<ACVS_TI>bf37ba1c9,iSTAR Server Compone</ACVS_TI>
<ACVS_C>ClusterPort</ACVS_C>
<ACVS_S>SoftwareHouse.NextGen.HardwareInterface.Nantucket.Framework.ClusterPort.HandleErrorState( )
</ACVS_S>
<ACVS_M>
ERROR MESSAGE FROM APP
</ACVS_M>
<ACVS_ST>
</ACVS_ST>
</ACVS_T>
solved it!
$File = 'D:\Program Files (x86)\Tyco\CrossFire\Logging\SystemTrace.Log'
$content = Get-Content -Path $file
# get stream of text
$text = ''
Get-Content $file -wait -Tail 0 | ForEach-Object {
$text +=$_
if ($text -match '(<ACVS_T>)((.|\n)*)(<\/ACVS_T>)+'){
[xml]$XML = "<Root>" + $text + "</Root>"
$text='' #clear it for next one
$XML.Root.ACVS_T | ForEach-Object {
$Obj = '' | Select-Object -Property ACVS_D, ACVS_TI, ACVS_C, ACVS_S, ACVS_M, ACVS_ST
$Obj.ACVS_D = $_.ACVS_D
$Obj.ACVS_ST = $_.ACVS_ST
$Obj.ACVS_C = $_.ACVS_C
$Obj.ACVS_S = $_.ACVS_S
$Obj.ACVS_M = $_.ACVS_M
$Obj.ACVS_ST = $_.ACVS_ST
write-host "`n`n$($Obj.ACVS_M)"
}
}
}

Automate a search for members of the local 'Administators' group

I have a PowerShell script named script1.ps1 that work perfectly. Here is the script:
Write-Host Script to display members of the local -ForegroundColor Green
Write-Host Administators group of a remote server. -ForegroundColor Green
Write-Host "`n"
$strComputer = Read-Host "Please enter the computer name"
$computer = [ADSI]("WinNT://" + $strComputer + ",computer")
$group = $computer.PSBase.Children.Find("administrators")
Write-Host ""
Write-Host "Computer Name : "$computer.Name
Write-Host "_____________________________________"
Write-Host ""
Write-Host "Group Name : "$Group.Name
Write-Host "_____________________________________"
$domain = $group.Path.Split("/")[2]
$string1 = "WinNT://" + $domain + "/" + $strComputer + "/"
$string2 = $strComputer + "/"
$string3 = "WinNT://"
$members = ($group.PSBase.Invoke("Members") | Foreach-Object {$_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)}) -replace ($string1,$string2) -replace ($string3,"")
Write-Host ""
Write-Host "Members : "
Write-Host ""
$members
But as you can see, I'm obliged to write each time the name of computer I want.
I ask myself if there is a function or other things which take automatically the computers names from a text or CSV file?
Like this at the beginning of the script, it automatically take all the computers names & give me the members of the local 'Administators' group which then gonna be exported in one text file or CSV file too?
Updated script according to the answer given.
Write-Host Script to display members of the local -ForegroundColor Green
Write-Host Administators group of a remote server. -ForegroundColor Green
Write-Host "`n"
Get-Content 'C:\Users\herbautr\Desktop\List1.txt' | ForEach-Object {
Write-Host "-$_-"
$computer = [ADSI]("WinNT://" + $_ + ",computer")
$group = $computer.PSBase.Children.Find("administrators")
Write-Host ""
Write-Host "Computer Name : "$computer.Name
Write-Host "_____________________________________"
Write-Host ""
Write-Host "Group Name : "$Group.Name
Write-Host "_____________________________________"
$domain = $group.Path.Split("/")[2]
$string1 = "WinNT://" + $domain + "/" + $_ + "/"
$string2 = $_ + "/"
$string3 = "WinNT://"
$members = ($group.PSBase.Invoke("Members") | Foreach-Object {$_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)}) -replace ($string1,$string2) -replace ($string3,"")
Write-Host ""
Write-Host "Members : "
Write-Host ""
$members
} | Set-Content 'C:\Users\herbautr\Desktop\administrators.txt'
I have add 1 computer name to the List1.txt:
01SPEAI-TEST1
01SPEAI-TEST2
02SPHPV-TEST1
01SLCPTAAP-PROD
And it works (not) perfectly (Unreadable layout)
01SPEAI-PROD1/Administrator
VNF-PRINCIPAL/Admins du domaine
VNF-PRINCIPAL/svceri
01SPEAI-PROD2/Administrator
VNF-PRINCIPAL/Admins du domaine
VNF-PRINCIPAL/svceri
02SPHPV-PROD1/Administrator
VNF-PRINCIPAL/Admins du domaine
01SLCPTAAP-PROD/Administrator
VNF-PRINCIPAL/Admins du domaine
01SLCPTAAP-PROD/maint
VNF-PRINCIPAL/svcoraas
VNF-PRINCIPAL/svcvisionit
VNF-PRINCIPAL/GopOAS
VNF-PRINCIPAL/svcdigora
Note (15:18pm): I have tried with 5 names, it continue to work.
Why when adding just 1 name it "works"?
You're looking for Get-Content and Set-Content.
Get-Content 'C:\path\to\computers.txt' | ForEach-Object {
$computer = [ADSI]("WinNT://" + $_ + ",computer")
...
} | Set-Content 'C:\path\to\administrators.txt'
Note that you need to replace all occurrences of $strComputer inside the ForEach-Object loop with the current object automatic variable ($_).
If you want to use CSVs for input and output use the Import-Csv and Export-Csv cmdlets.
Import-Csv 'C:\path\to\computers.csv' | ForEach-Object {
$computer = [ADSI]("WinNT://" + $_.ComputerName + ",computer")
...
$members | ForEach-Object {
New-Object -Type PSObject -Property #{
Member = $_
}
}
} | Export-Csv 'C:\path\to\administrators.csv' -NoType
Note that CSVs have some advantages when you need to handle items with multiple properties, but they require somewhat more elaborate handling than simple strings (as you can see in my example above).

Powershell search matching string in word document

I have a simple requirement. I need to search a string in Word document and as result I need to get matching line / some words around in document.
So far, I could successfully search a string in folder containing Word documents but it returns True / False based on whether it could find search string or not.
#ERROR REPORTING ALL
Set-StrictMode -Version latest
$path = "c:\MORLAB"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "c:\wordfiletry.txt"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "CRHPCD01"
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
$wordFound = $range.find.execute($findText)
if($wordFound)
{
"$file.fullname has $wordfound" | Out-File $output -Append
}
}
$document.close()
$application.quit()
}
getStringMatch
#ERROR REPORTING ALL
Set-StrictMode -Version latest
$path = "c:\Temp"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "c:\temp\wordfiletry.csv"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First"
$charactersAround = 30
$results = #{}
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
If($range.Text -match ".{$($charactersAround)}$($findtext).{$($charactersAround)}"){
$properties = #{
File = $file.FullName
Match = $findtext
TextAround = $Matches[0]
}
$results += New-Object -TypeName PsCustomObject -Property $properties
}
}
If($results){
$results | Export-Csv $output -NoTypeInformation
}
$document.close()
$application.quit()
}
getStringMatch
import-csv $output
There are a couple of ways to get what you want. A simple approach is since you have the text of the document already lets perform a regex match on it and return the results and more. This helps in trying to address getting some words around in document.
We have the variable $charactersAround which sets the number of characters to match around the $findtext. Also I though the output was a better fit for a CSV file so I used $results to capture a hashtable of properties that, in the end, are output to a csv file.
Be sure to change the variables for your own testing. Now that we are using regex to locate the matches this opens up a world of possibilities.
Sample Output
Match TextAround File
----- ---------- ----
First dley Air Services Limited dba First Air meets or exceeds all term C:\Temp\20120315132117214.docx
Thanks! You provided a great solution to use PowerShell regex expressions to look for information in a Word document. I needed to modify it to meet my needs. Maybe, it will help someone else. It reads each line of the word document, and then uses the regex expression to determine if the line is a match. The output could easily be modified or dumped to a log file.
Set-StrictMode -Version latest
$path = "c:\Temp\pii"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "[0-9]" #regex
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files) {
$document = $application.documents.open($file.FullName,$false,$true)
$arrContents = $document.content.text.split()
$varCounter = 0
ForEach ($line in $arrContents) {
$varCounter++
If($line -match $findtext) {
"File: $file Found: $line Line: $varCounter"
}
}
$document.close()
}
$application.quit()
}
getStringMatch
Good answer from #Matt.
I improved it a little (new PowerShell version have problems with the given array. And to search big amount of documents it runs out of memory.
Here is my improved version:
#ERROR REPORTING ALL
Set-StrictMode -Version latest
$path = "c:\Temp"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "c:\temp\wordfiletry.csv"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First"
$charactersAround = 30
$results = #{}
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
If($range.Text -match ".{$($charactersAround)}$($findtext).{$($charactersAround)}"){
$properties = #{
File = $file.FullName
Match = $findtext
TextAround = $Matches[0]
}
$results += #(New-Object -TypeName PsCustomObject -Property $properties)
}
$document.close()
}
If($results){
$results | Export-Csv $output -NoTypeInformation
}
$application.quit()
}
getStringMatch
import-csv $output
Use the function like this:
PS> WordGrep -File ./Myfile.docx -Grep one, two, three
function WordGrep{
param(
[string]$File,
[string[]]$Grep,
[switch]$WordMode,
[switch]$EscapeMode
)
$WordApp = New-Object -comobject word.application
$WordApp.visible = $False
try {
$document = $WordApp.documents.open($File, $false, $true)
$arrContents = $document.content.text.split()
$found = $false
foreach ($line in $arrContents) {
foreach ($pattern in $Grep) {
if ($EscapeMode) {
$pattern = [Regex]::Escape($pattern)
}
if ($WordMode) {
$pattern = "\b${pattern}\b"
}
if ($line -imatch $pattern) {
write-host -ForegroundColor Cyan -NoNewLine "$file`:"
write-host " $line"
break;
}
}
}
$document.close()
}
finally {
$WordApp.quit()
}
}