Efficiently search a string in large files - string

How can I check if a string exists in:
1 text file;
size up until 10GB;
taking into account that the file is only one line;
the file only contains random numbers 1 to 9;
using powershell (because I think it will be more efficient, although I don't know how to program in this language);
I have tried this in batch:
FINDSTR "897516" decimal_output.txt
pause
But as I said I need the faster and more efficient way to do this.
I also tried this code that I have found in stackoverflow:
$SEL = Select-String -Path C:\Users\fabio\Desktop\CONVERTIDOS\dec_output.txt -Pattern "123456"
if ($SEL -ne $null)
{
echo Contains String
}
else
{
echo Not Contains String
}
But I get the error below, and I don't know if this code is the most solid or adequate. The error:
Select-String : Tipo de excepção 'System.OutOfMemoryException' accionado.
At C:\Users\fabio\Desktop\1.ps1:1 char:8
+ $SEL = Select-String -Path C:\Users\fabio\Desktop\CONVERTIDOS\dec_out ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Select-String], OutOfMemoryException
+ FullyQualifiedErrorId : System.OutOfMemoryException,Microsoft.PowerShell.Commands.SelectStringCommand

This should do the job:
#################################################################################################################
#
# Searches for a user defined string in the $input_file and counts matches. Works with files of any size.
#
# Adjust source directory and input file name.
#
$source = "C:\adjust\path"
$input_file = "file_name.extension"
#
#
# Define the string you want to search for. Keep quotation marks even if you only search for numbers (otherwise
# $pattern.Length will be 1 and this script will no longer work with files larger than the $split_size)!
#
$pattern = "Enter the string to search for in here"
#
#
# Using Get-Content on an input file with a size of 1GB or more will cause System.OutOfMemoryExceptions,
# therefore a large file gets temporarily split up.
#
$split_size = 100MB
#
#
# Thanks #Bob (https://superuser.com/a/1295082/868077)
#################################################################################################################
Set-Location $source
if (test-path ".\_split") {
while ($overwrite -ne "true" -and $overwrite -ne "false") {
"`n"
$overwrite = Read-Host ' Splitted files already/still exist! Delete and overwrite?'
if ($overwrite -match "y") {
$overwrite = "true"
Remove-Item .\_split -force -recurse
$a = "`n Deleted existing splitted files!"
} elseif ($overwrite -match "n") {
$overwrite = "false"
$a = "`n Continuing with existing splitted files!"
} elseif ($overwrite -match "c") {
exit
} else {
Write-Host "`n Error: Invalid input!`n Type 'y' for 'yes'. Type 'n' for 'no'. Type 'c' for 'cancel'. `n`n`n"
}
}
}
Clear-Host
if ((Get-Item $input_file).Length -gt $split_size) {
while ($delete -ne "true" -and $delete -ne "false") {
"`n"
$delete = Read-Host ' Delete splitted files afterwards?'
if ($delete -match "y") {
$delete = "true"
$b = "`n Splitted files will be deleted afterwards!"
} elseif ($delete -match "n") {
$delete = "false"
$b = "`n Splitted files will not be deleted afterwards!"
} elseif ($delete -match "c") {
exit
} else {
Write-Host "`n Error: Invalid input!`n Type 'y' for 'yes'. Type 'n' for 'no'. Type 'c' for 'cancel'. `n`n`n"
}
}
Clear-Host
$a
$b
Write-Host `n This may take some time!
if ($overwrite -ne "false") {
New-Item -ItemType directory -Path ".\_split" >$null 2>&1
[Environment]::CurrentDirectory = Get-Location
$bytes = New-Object byte[] 4096
$in_file = [System.IO.File]::OpenRead($input_file)
$file_count = 0
$finished = $false
while (!$finished) {
$file_count++
$bytes_to_read = $split_size
$out_file = New-Object System.IO.FileStream ".\_split\_split_$file_count.splt",CreateNew,Write,None
while ($bytes_to_read) {
$bytes_read = $in_file.Read($bytes, 0, [Math]::Min($bytes.Length, $bytes_to_read))
if (!$bytes_read) {
$finished = $true
break
}
$bytes_to_read -= $bytes_read
$out_file.Write($bytes, 0, $bytes_read)
}
$out_file.Dispose()
}
$in_file.Dispose()
}
$i++
while (Test-Path ".\_split\_split_$i.splt") {
$cur_file = (Get-Content ".\_split\_split_$i.splt")
$temp_count = ([regex]::Matches($cur_file, "$pattern")).Count
$match_count += $temp_count
$n = $i - 1
if (Test-Path ".\_split\_split_$n.splt") {
if ($cur_file.Length -ge $pattern.Length) {
$file_transition = $prev_file.Substring($prev_file.Length - ($pattern.Length - 1)) + $cur_file.Substring(0,($pattern.Length - 1))
} else {
$file_transition = $prev_file.Substring($prev_file.Length - ($pattern.Length - 1)) + $cur_file
}
$temp_count = ([regex]::Matches($file_transition, "$pattern")).Count
$match_count += $temp_count
}
$prev_file = $cur_file
$i++
}
} else {
$a
$match_count = ([regex]::Matches($input_file, "$pattern")).Count
}
if ($delete -eq "true") {
Remove-Item ".\_split" -Force -Recurse
}
if ($match_count -ge 1) {
Write-Host "`n`n String '$pattern' found:`n`n $match_count matches!"
} else {
Write-Host "`n`n String '$pattern' not found!"
}
Write-Host `n`n`n`n`n
Pause
This will split a large file into mutliple smaller files, search them for $pattern and count the matches (taking file transitions into account).
It also offers you to delete or keep the splitted files afterwards so you can reuse them and don't have to split the large file every time you run this script.

Related

I am parsing RoboCopy logs from Millions of files, How can I make my code run Faster?

New to StackOverflow, I'll do my best to post correctly :)
Hoping someone can help me to get my code running faster.
The code is run against RoboCopy Migration logs from a massive DFS server migration (20 DFS servers being migrated).
The code first captures the source/destination of the log in question and then looks for the 'Newer', 'Older', 'New File' and 'Extra File' entries/rows. It then checks to see if these files exist at each side, what attributes they have and does a DFSR hash check against both sides (as the files are now being replicated via DFSR).
The main concern is if the hashes match for source and destination and if the temporary attribute is in place.
The problem I am having is that there are millions of files logged under these types (the migration was gargantuan) so the script is taking forever to run. To add to this the client will not allow ports for psremoting/invoke-command.
At present I am running my code without multi-threading, with a copy on each of the DFS servers looking at their respective logs but it is still slow.
I have been looking at running a foreach parallel on looping through each log row (not the loop of log files) but:
With so much data within each log/loop my understanding is that I have to write it out rather than keep it in an PsCustomObject? Otherwise I would run out of RAM?
I don't really understand how to use MUTEXes to get multiple writes to the CSV.
Can someone please advise me on the above 2 points? And maybe give me some more ideas on what I can do to optimise things?
My full code is below..
#Get Start Time
$ReportStartTime = (Get-Date).ToString('yyy-MM-dd_HH-mm-ss')
If(!(test-path "C:\Temp\MasterReport_$ReportStartTime\")){
new-item -type directory -path "C:\Temp\MasterReport_$ReportStartTime\" | Out-Null
}
"Script Started:$ReportStartTime" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
#Get Logs from folder (Recursive)
$Logs = Try{
Get-ChildItem -path 'C:\Temp\RoboCopyLogs\*\*.log' -Recurse -ErrorAction Stop | Select FullName
}
catch{
$_.Exception >> "C:\Temp\MasterReport_$ReportStartTime\Errors_$ReportStartTime.log"
}
#Initialise Log Counters
$NumberOfFiles = 0
$DesktopFile = 0
$ProcessedFiles = 0
$Totalsize = 0
#Count Logs
$Logs | foreach {
$SourceLog = $_
#Get Logfile
$Log = Get-Content $SourceLog.FullName
#Get Log rows for required Error Types and begin loop
$Log | Select-String -Pattern '(^\t+ +(Newer|Older|New File|Extra File))' `
|foreach {
$NumberOfFiles=$NumberOfFiles+1
If($_ | Select-String -pattern 'Desktop.ini' -SimpleMatch){
$DesktopFile=$DesktopFile+1
}
}
}
$Expected = $NumberOfFiles - $DesktopFile
"Total Files To Check = $NumberOfFiles" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
"Total Files Excluded = $DesktopFile" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
"Total Files To Ingest = $Expected" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
$Main = (Get-Date).ToString('yyy-MM-dd_HH-mm-ss')
"Main Script:$Main" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
$Logs | foreach {
$SourceLog = $_
#Get Logfile
$Log = Get-Content $SourceLog.FullName
#Collect Source and Destination
$S = $Log | Select-String -Pattern 'Source :'
$D = $Log | Select-String -Pattern 'Dest :'
$SourceLocation = $S -replace '\s+Source : ',''
$DestLocation = $D -replace '\s+Dest : ',''
#Get Log rows for required Error Types and begin loop
$Log | Select-String -Pattern '(^\t+ +(Newer|Older|New File|Extra File))' | Select-String -pattern 'Desktop.ini' -SimpleMatch -NotMatch `
|foreach {
#This loop could be a foreach -parallel???
#Check Percent Completed
If($ProcessedFiles>0){
$PercentComplete=[Math]::Ceiling(($ProcessedFiles/$Expected)*100)
If($PercentComplete -match ('([0-9]0)')){
"$($PercentComplete)% Completed" > "C:\Temp\MasterReport_$ReportStartTime\PercentComplete.Log"
($ProcessedFiles/$Expected)*100
}
}
#Count Logs Processed
$ProcessedFiles=$ProcessedFiles+1
#Populate FilePath
$FilePath = $_ -Replace '.*(?=\\\\)', ''
#Populate Error type
$RoboErrorRaw = $_ -replace '\s+','|'
$RoboError = $RoboErrorRaw.split("|")[1]
#Check if file path relates to Source or the Destination and set path variables
if($FilePath -like "$SourceLocation*"){
$SourceFilePath = $FilePath
$DestFilePath = $FilePath.replace($SourceLocation,$DestLocation)
}
Elseif($FilePath -like "$DestLocation*"){
$DestFilePath = $FilePath
$SourceFilePath = $FilePath.replace($DestLocation,$SourceLocation)
$IsAtPartner = Test-Path $SourceFilePath
}
Else{
$DestFilepath = "Could Not Resolve UNC to Source or Destination"
}
#Check if file exists at source and destination
Try{
$IsAtPartner = Test-Path $DestFilePath -ErrorAction Stop
}
catch{
$IsAtPartner = $_.Exception
}
Try{
$IsAtSource = Test-path $SourceFilePath -ErrorAction Stop
}
catch{
$IsAtSource = $_.Exception
}
If($IsAtSource){
#Get the file details
Try{
$SourceFileDetails = Get-ChildItem $FilePath -Hidden -ErrorAction Stop
}
catch{
$SourceFileDetails = 'Failed'
}
if($SourceFileDetails -ne 'Failed'){
#Check has temp attribute
if((($SourceFileDetails).Attributes -band 0x100) -eq 0x100){
$TempAttribute = "Yes"
}
Else{
$TempAttribute = "No"
}
#Get attributes and last modified
Try{
$AllAttributes = ($SourceFileDetails).Attributes
}
catch{
$AllAttributes = $_.Exception
}
Try{
$Modified = ($SourceFileDetails).LastWriteTime.ToString()
}
catch{
$Modified = $_.Exception
}
}
}
#Check if .bak file
if($filePath -match '\.bak$'){
$Bakfile = "Yes"
}
Else{
$Bakfile = "No"
}
#Get Hashes
If($IsAtPartner -and $IsAtSource){
$HashSource = (Get-DfsrFileHash -Path $SourceFilepath).FileHash
$HashDest = (Get-DfsrFileHash -Path $DestFilepath).FileHash
}
ElseIf(!$IsAtSource -and !$IsAtPartner){
$HashSource = 'File Does not Exist at Source'
$HashDest = 'File Does not Exist At Partner'
}
ElseIf(!$IsAtPartner){
$HashSource = (Get-DfsrFileHash -Path $SourceFilepath).FileHash
$HashDest = 'File Does not Exist At Partner'
}
ElseIf(!$IsAtSource){
$HashSource = 'File Does not Exist at Source'
$HashDest = (Get-DfsrFileHash -Path $DestFilepath).FileHash
}
Else{
$HashSource = 'ERROR'
$HashDest = 'ERROR'
}
#Compare Valid Hashes
If($HashSource -eq $HashDest){
$HashMatch = 'Yes'
}
Else{
$HashMatch = 'No'
}
#Check Filesize where hashes do not match
If($HashMatch = 'No'){
$FileSizeMB = ($SourceFileDetails).length/1MB
}
#Create output object
$Obj = [PSCustomObject]#{
ErrorType = $RoboError
FilePath = $SourceFilePath
PartnerUNC = $DestFilePath
IsAtSource = $IsAtSource
IsAtDestination = $IsAtPartner
BakFile = $Bakfile
TepmpAttribute = $TempAttribute
LastModified = $Modified
AllAttributes = $AllAttributes
HashSource = $HashSource.FileHash
HashDest = $HashDest.FileHash
HashMatch = $HashMatch
RoboSource = $SourceLocation
RoboDest = $DestLocation
FileSizeMB = $FileSizeMB
SourceLog = $SourceLog.FullName
}
$Source = $SourceLocation.split('\\')[2]
$Destination = $DestLocation.split('\\')[2]
if(!(test-path "C:\Temp\$($Source)-$($Destination)_$($ReportStartTime)")){
new-item -type directory -path "C:\Temp\$($Source)-$($Destination)_$($ReportStartTime)" | Out-Null
}
#export to csv
$obj | Export-Csv -Path "C:\Temp\$($Source)-$($Destination)_$($ReportStartTime)\RoboCopyLogChecks_$ReportStartTime.csv" -NoTypeInformation -Append
$obj | Export-Csv -Path "C:\Temp\MasterReport_$ReportStartTime\RoboCopyLogChecks_$ReportStartTime.csv" -NoTypeInformation -Append
#Increment total size of data
If($HashMatch -eq "Yes"){
$Totalsize = $Totalsize + $SourceFileDetails.Length
}
clear-variable -name RoboError,SourceFilePath,DestFilePath,IsAtSource,IsAtPartner,Bakfile,TempAttribute,Modified,AllAttributes,HashSource,HashDest,HashMatch,FileSizeMB,Source,Destination
if($SourceFileDetails){
Remove-Variable -name SourceFileDetails
}
}
}
$Completion = (Get-Date).ToString('yyy-MM-dd_HH-mm-ss')
"Script Completed:$Completion Excluded Processed = $DesktopFile ,Total Processed = $ProcessedFiles" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
"Files without Matching Hashses amount to $($Totalsize/1GB)GB" >> "C:\Temp\MasterReport_$ReportStartTime\Log_$ReportStartTime.Log"
Here is some example log data (could be put in C:\Temp\RoboCopyLogs\Logs\ to run with above code)
-------------------------------------------------------------------------------
ROBOCOPY :: Robust File Copy for Windows
-------------------------------------------------------------------------------
Started : 24 April 2022 17:29:57
Source : \\Test01\
Dest : \\Test02\
Files : *.*
Exc Files : ~*.*
*.TMP
Exc Dirs : \\Test01\DfsrPrivate
Options : *.* /FFT /TS /L /S /E /DCOPY:DA /COPY:DAT /PURGE /MIR /B /NP /XJD /MT:8 /R:0 /W:0
------------------------------------------------------------------------------
Newer 30720 2021/07/20 14:49:36 \\Test01\Test2121.xls
Older 651776 2020/10/25 21:49:32 \\Test01\testppt.ppt
Older 94720 2019/06/10 11:46:03 \\Test01\Thumbs.db
*EXTRA File 1.7 m 2020/09/17 10:36:57 \\Test02\months.jpg
*EXTRA File 1.8 m 2020/09/17 10:36:57 \\Test02\happy.jpg
New File 6421 2020/10/26 10:32:43 \\Test01\26-10-20.pdf
New File 6321 2020/10/26 10:32:43 \\Test01\Testing20.pdf

PowerShell: Working with Strings and Hashtables

I have this code:
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$parametersList = #($passwordsParameters -split '&&&')
$passwordsTable = #{}
ForEach ($parameter in $parametersList) {
$splitToKeyValue = #($parameter -split '=')
$passwordsTable += $passwordsTable = #{
$splitToKeyValue[0].trim() = $splitToKeyValue[1].trim()
}
}
ForEach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass] -ne "") {
Write-Host "set $pass ="$passwordsTable[$pass]""
} else {
Write-Host "A value for the parameter $pass was not entered."
}
}
# Add-Content "d:\myFile.txt" "set $pass ="$passwordsTable[$pass]""
Which perfectly works when I use Write-Host. But I want to do something like in the comment in line 25. I tried several ways but I always got a static string instead of the values that I get from the Hashtable.
At the end I want to have something like:
set pass1 = 12
set pass2 = 5
in myFile.txt
Any help is appreciated. Thanks!
You could change Write-Host (just prints to a console) to Write-Output ( which passes an object to a pipeline). Write-Output does not print to the console.
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$parametersList = #($passwordsParameters -split '&&&')
$passwordsTable = #{}
ForEach ($parameter in $parametersList) {
$splitToKeyValue = #($parameter -split '=')
$passwordsTable += $passwordsTable = #{
$splitToKeyValue[0].trim() = $splitToKeyValue[1].trim()
}
}
$counter=0
ForEach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass] -ne "") {
$counter++
Write-Output "set "pass$counter = $passwordsTable[$pass]"`n" | Add-Content -NoNewline myFile.txt
} else {
Write-Host "A value for the parameter $pass was not entered."
}
}
Output:
set pass1=10
set pass2=13
set pass3=12
You can replace the first foreach loop if you simply replace all the &&& by a newline and use cmdlet ConvertFrom-StringData.
Add-Content also has a switch called -PassThru that will let you write to the file and also output to console.
$passwordsParameters = "sysPassword = 12 &&& testPass = 13 &&& systemPassword = 10"
$passwordsTable = $passwordsParameters -replace '&&&', [Environment]::NewLine | ConvertFrom-StringData
foreach ($pass in $passwordsTable.Keys) {
if ($passwordsTable[$pass]) {
$msg = 'set {0} = {1}' -f $pass, $passwordsTable[$pass]
# or use: $msg = "set $pass = $($passwordsTable[$pass])"
# write this to the file. switch -PassThru will also output to the console
Add-Content -Path 'D:\myFile.txt' -Value $msg -PassThru
} else {
Write-Host "A value for the parameter '$pass' was not entered."
}
}

Replacing multiple texts not getting saved

I've a bunch of files in which I need to replace content like for e.g. wherever there is 'AA' I need to replace with 'E1', 'A1' with 'P4'. The same content needs to be changed differently in different files. So for example in the 2nd file 'AA' would become 'P1', 'A1' would become 'E1', etc. To accomplish this I've an Excel sheet with 2 columns like the below:
TC CodeChange
086 AA-E1; A1-P2
099 AA-P2; A1-E1; A2-E2; Z3-E3
100 AA-P2; A1-E2; A2-E3; Z3-O3
PowerShell script which I wrote for the above:
Script 1:
function func3 {
Param($arr3, $pat)
$arr3.GetEnumerator() | ?{$_.key -like $pat} | ForEach-Object {
$output = $_.value
return $output
}
}
$src = "C:\...xlsx"
$src1 = "C:\...\..."
$sheetName = "Sheet1"
$arr = #{};
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($src)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).count
$rowTC, $colTC = 1, 1
$rowCodeChange, $colCodeChange = 1, 2
for ($i=1; $i -le $rowMax-1; $i++) {
$TC = $sheet.Cells.Item($rowTC+$i, $colTC).Text
$CodeChg = [String]($sheet.Cells.Item($rowCodeChange+$i, $colCodeChange).Text)
if ($arr.ContainsKey($TC) -eq $false) {
$arr.Add($TC, $CodeChg)
}
}
$inputfiles = (Get-ChildItem -Path $src1 -Recurse)
foreach ($inputfile in $inputfiles) {
$pat1 = $inputfile.Name.SubString(8, 3)
$val = func3 $arr $pat1
$arry1 = $val -split ';'
Write-Host $arry1.Length
$j = 0
do {
#skipping these 3 items from getting replaced
if (($arry1[$j].Trim() -ne "S1") -and ($arry1[$j].Trim() -ne "S2") -and ($arry1[$j].Trim() -ne "S3")) {
(Get-Content $inputfile.FullName) | ForEach-Object {
$_ -replace "$($arry1[$j].Split('-')[0])","$($arry1[$j].Split('-')[1])"
} | Set-Content $inputfile.FullName
}
$j++
} while ($j -le ($arry1.Length-1))
}
$objExcel.Quit()
Script 2:
function func3 {
param($arr3, $pat)
$arr3.GetEnumerator() | ?{$_.key -like $pat} | ForEach-Object {
$output=$_.value
return $output
}
}
$src = "C:\...xlsx"
$src1 = "C:\..."
$sheetName = "Sheet1"
$arr = #{};
$objExcel = New-Object -ComObject Excel.Application
$workbook = $objExcel.Workbooks.Open($src)
$sheet = $workbook.Worksheets.Item($sheetName)
$objExcel.Visible = $false
$rowMax = ($sheet.UsedRange.Rows).Count
$rowTC, $colTC = 1, 1
$rowCodeChange, $colCodeChange = 1, 2
for ($i=1; $i -le $rowMax-1; $i++) {
$TC = $sheet.Cells.Item($rowTC+$i, $colTC).Text
$CodeChg = [String]($sheet.Cells.Item($rowCodeChange+$i, $colCodeChange).Text)
if ($arr.ContainsKey($TC) -eq $false) {
$arr.Add($TC, $CodeChg)
}
}
$inputfiles = (Get-ChildItem -Path $src1 -Recurse)
foreach ($inputfile in $inputfiles) {
$pat1 = $inputfile.Name.SubString(8, 3)
$val = func3 $arr $pat1
$arry1 = $val -split ';'
Write-Host $arry1.Length
$j = 0
do {
#skipping these 3 items from getting replaced
if (($arry1[$j].Trim() -ne "S1") -and ($arry1[$j].Trim() -ne "S2") -and ($arry1[$j].Trim() -ne "S3")){
$content = [System.IO.File]::ReadAllText($inputfile.FullName).Replace($arry1[$j].Split('-')[0], $arry1[$j].Split('-')[1])
[System.IO.File]::WriteAllText($inputfile.FullName, $content)
Write-Host $arry1[$j].Split('-')[0]' replaced with '$arry1[$j].Split('-')[1]' in file: '$inputfile.FullName
}
$j++
} while ($j -le ($arry1.Length-1))
}
$objExcel.Quit()
The folder where the files are has the files having names containing the same digits in the 'TC' column in my Excel sheet. Example:
TC 086.txt
TC 099.txt
etc.
That way after I import the contents of the Excel into a hashtable I extract the digits from the filenames and get the corresponding values for the same key in the hashtable. For example the value for the key '086' from the hashtable would be 'AA-E1; A1-P2'. Then I split the items to be replaced from the hashtable value (separated by ;) and then store that in an array. The using a loop I try to replace the contents of each file based on the data retrieved from the spreadsheet.
The issue I'm facing with both the approaches is that only the 1st item in each file is getting replaced. The rest of the items are not getting replaced. For example only 'AA' value in file 'TC 086.txt' is getting replaced with 'E1'. 'A1' is not getting replaced with 'P2'.
I found out what the issue was. I basically had to trim the elements of the array
$arry1
after splitting them (separated by ;) and before passing them as parameters to the 'Replace' function. Apparently there was a space before every element in that array except the 1st element (that's how they were stored in the source: excel spreadsheet). Hence the 'Replace' method was not finding that element in the file and hence not replacing it. Removing the spaces before the elements solved the issue

Replacing value on one line in a text file

I am currently working on editing one line of a text file. When I try to overwrite the text file, I only get one line back in the text file. I am trying to call the function with
modifyconfig "test" "100"
config.txt:
check=0
test=1
modifyConfig() function:
Function modifyConfig ([string]$key, [int]$value){
$path = "D:\RenameScript\config.txt"
((Get-Content $path) | ForEach-Object {
Write-Host $_
# If '=' is found, check key
if ($_.Contains("=")){
# If key matches, replace old value with new value and break out of loop
$pos = $_.IndexOf("=")
$checkKey = $_.Substring(0, $pos)
if ($checkKey -eq $key){
$oldValue = $_.Substring($pos+1)
Write-Host 'Key: ' $checkKey
Write-Host 'Old Value: ' $oldValue
$_.replace($oldValue,$value)
Write-Host "Result:" $_
}
} else {
# Do nothing
}
}) | Set-Content ($path)
}
The result I receive in my config.txt:
test=100
I am missing "check=0".
What have I missed?
$_.replace($oldValue,$value) in your innermost conditional replaces $oldValue with $value and then prints the modified string, but you don't have code printing non-matching strings. Because of that only the modified string are written back to $path.
Replace the line
# Do nothing
with
$_
and also add an else branch with a $_ to the inner conditional.
Or you could assign $_ to another variable and modify your code like this:
Foreach-Object {
$line = $_
if ($line -like "*=*") {
$arr = $line -split "=", 2
if ($arr[0].Trim() -eq $key) {
$arr[1] = $value
$line = $arr -join "="
}
}
$line
}
or a one liner.. (not exactly pin pointed answer, but to the question title)
(get-content $influxconf | foreach-object {$_ -replace "# auth-enabled = false" , "auth-enabled = true" }) | Set-Content $influxconf

PowerShell - Enumerating through a collection and change the collection

How it is posible to fix this script?
Yes, I´m changing the collection in the foreach loop and this is the reason for this error.
An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not execute..
At C:\Users\user\Documents\PowerShell\ChangeAllListsV2.ps1:47 char:20
+ foreach <<<< ($list in $webLists)
+ CategoryInfo : InvalidOperation: (Microsoft.Share...on+SPEnumerator:SPEnumerator) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
#Script change in all lists the required field property "testfield" to false
#Part 0 - Configuration
$urlWebApp = "http://dev.sharepoint.com"
$countFound = 0
$countList = 0
$countFoundAndChange = 0
#Part 1 - PreScript
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq "Microsoft.SharePoint.Powershell"}
if ($snapin -eq $null)
{
Write-Host “Loading SharePoint Powershell”
Add-PSSnapin Microsoft.SharePoint.Powershell
}
#Part 2 - Script
$webApp = Get-SPWebApplication $urlWebApp
#$webApp | fl
$webAppSites = $webApp.sites
foreach($site in $webAppSites)
{
Write-Host "***********************************************************************"
Write-Host "Found site: " $site -foreground blue
$siteAllWebs = $site.AllWebs
foreach($web in $siteAllWebs)
{
Write-Host "Found web: " $web -foreground blue
#$web | fl
$webLists = $web.Lists
foreach($list in $webLists)
{
$countList ++
Write-Host "Found list: " $list -foreground blue
#Change list property
$field = $Null
$field = $list.Fields["testfield"]
if($field){
Write-Host "Field found: " $list -foreground green
#Write-Host "in web: " $web -foreground green
$countFound ++
try{
if($field.Required)
{
#######################################################
$field.Required = $False
$field.Update()
#######################################################
$field = $Null
Write-Host "Done!: Change list: " $list -foreground green
$countFoundAndChange ++
}else{
Write-Host "Already!: Change list: " $list -foreground green
}
}
catch{
$field = $Null
Write-Host "Error!: Change list: " $list -foreground red
Write-Host "in web: " $web -foreground red
$_
}
}
}
}
}
Write-Host "Found lists: " $countList
Write-Host "Found lists with column [testfield]: " $countFound
Write-Host "Change lists with column [testfield]: " $countFoundAndChange
The SPListCollection tends to modify the collection when updating its properties (fields, event receivers, etc.). You can use a for-loop instead:
for ($i = 0; $i -lt $webLists.Count; $i++)
{
$list = $web.Lists[$i];
# ...
}
I know this is a pretty old thread. This is for anybody ending up to this page looking for an answer.
The idea is, like other answers suggest, to copy the collection (using the clone() method) to another and iterate "another" and modify the original variable inside the loop without having to use for in place of foreach:
A collection of type ArrayList:
[System.Collections.ArrayList]$collection1 = "Foo","bar","baz"
$($collection1.Clone()) | foreach {
$collection1.Remove("bar")
}
Output:
PS H:\> $collection1
Foo
baz
A collection of type Hashtable:
[System.Collections.Hashtable]$collection2 = #{
"Forum" = "Stackoverflow"
"Topic" = "PowerShell"
}
$($collection2.Clone())| foreach {
$collection2.Remove("Forum")
}
Output:
PS H:> $collection2
Name Value
---- -----
Topic PowerShell
And, a basic array:
[System.Array]$collection3 = 1, 2, 3, 4
$($collection3.Clone()) | foreach {
$collection3[$collection3.IndexOf($_)] = 10
}
Output:
PS H:\> $collection3
10
10
10
10
As long as your collection is not of fixed size.
You can try copying the collection you're currently iterating on to another collection (an array or a list) and then iterate on that new collection.
Something like this:
$collection = #(1, 2, 3, 4)
$copy = #($collection)
$collection[0] = 10
$collection -join " "
$copy -join " "
The code above gives the following output:
10 2 3 4
1 2 3 4
Note that the $copy variable refers to a different collection.
Check: http://soreddymanjunath.blogspot.in/2014/07/collection-was-modified-enumeration.html
Here is anonther example for same issue
if($web.IsMultilingual -eq $true )
{
foreach($cul in $web.SupportedUICultures)
{
if($cul.LCID -ne $webCul.LCID -and $cul.LCID -ne "1033")
{
$web.RemoveSupportedUICulture($cul)
}
}
$web.Update()
}
for the first time it will go through the loop foreach will remove supported culture for frist time, when it comes to loop for the second iteration then it will throw you the exception “Collection was modified; enumeration operation may not execute”,
Solution to Above problem is to Store to values to modified in a Arraylist and try to modify which will fix the problem, Here i am storing Arraylist called enumcul and inserting values into it and modifying it...
$enumcul=New-Object Collections.ArrayList
$i=0
if($web.IsMultilingual -eq $true )
{
foreach($cul in $web.SupportedUICultures)
{
if($cul.LCID -ne $webCul.LCID -and $cul.LCID -ne "1033")
{
$enumcul.Insert($i, $cul)
$i=$i+1
}
}
foreach( $k in $enumcul)
{
$web.RemoveSupportedUICulture($k)
$web.Update()
}

Resources