Powershell: ForEach only looping once? - loops

I have a script that I've been working on, which reads a specified directory, locates .CSV files, and executes some logic for each of the .CSV files and ultimately renames them .csv.archived .
The code was working fine last night, however this morning, when I execute the code it only loops through once. For example, last night, I had 5 .csv files in the directory, it would return the file names for all 5 files in a single action. Now, each time I execute my script, it grabs the first file, performs the actions intended, and then exits forcing me to manually initiate the script for each file.
I've gutted the irrelevant code for testing purposes, and would be happy if someone could tell me that I am doing something wrong, and that I am not crazy.
Here's the code:
$iterations = 1
#set the location where the .CSV files will be pulled from
$Filecsv = get-childitem "\\SERVERPATH\Audit Test\" -recurse | where {$_.extension -eq ".csv"} | % {
$filename = $_.Name
}
#for each file found in the directory
ForEach ($Item in $Filecsv) {
#spit out the file name
"File Name: " + $filename
#count the times we've looped through
"Iterations : " + $iterations
# get the date and time from the system
$datetime = get-date -f MMddyy-hhmmtt
# rename the file
rename-item -path ("\\SERVERPATH\Audit Test\"+ $filename) -newname ($filename + $datetime + ".csv.archived")
$iterations ++
}
...and here is the Output:
For the example I showed you, I had four .CSV files in the directory. I had to manually execute my script, and each time it would perform as expected, but only for the first item it encounters in the directory. It doesn't actually loop through, what am I missing here?

Right here (folded at the pipe for readability):
$Filecsv = get-childitem "\\SERVERPATH\Audit Test\" -recurse |
where {$_.extension -eq ".csv"} |
% {$filename = $_.Name}
You're looping through your files and for each one setting $filename to the name of that file instead of letting the filenames accumulate in $Filecsv
$Filecsv = get-childitem "\\SERVERPATH\Audit Test\" -recurse |
where {$_.extension -eq ".csv"} |
% {$_.Name}

Related

Search multiple text files for string and copy line by appending it to summary_[date].log

I'm trying to search through a number of log files with different filenames. I want search for a hostname in each log and when a match is found have it copy that entire line to summary_[date].log and keep appending matching lines to it. So something I've started with is:
$captured = Get-ChildItem -recurse -Path \\nas1\share1 -Include *.log |
where { ($_.Name | Select-String -pattern ('PC1','PC2','PC3') -SimpleMatch) }
Now copy the line from each log file which contains the pattern and append it to a file with today's date stamp, so each week I'll have a file like \\nas1\share1\summary_03-07-2020.log
But this is not quite what I want as this will capture the filenames and append them to the $captured variable. It's also missing the code to copy any matching lines to a date stamped summary_[date].log
Each text file will contain, among other lines that start with a time stamp, something like this:
03-07-2020_14-36-17 - Backup of computer [PC1] is successfully
created.
So what I want is to search several text files on a share for several hostnames. If a text file contains the hostname have it append the line which contains the hostname to summary_[date].log. Lastly, since the matching lines will all start with a date/time stamp I need to keep the contents of summary_[date].log file sorted from newest date to oldest.
Essentially I should end up with a summary_[date].log every week that will look similar to this:
03-07-2020_14-36-17 - Backup of computer [PC1] is successfully created.
03-07-2020_13-21-12 - Backup of computer [PC3] is successfully created.
03-07-2020_11-36-29 - Backup of computer [PC2] is successfully created.
By doing this I get a summary of all log files from that day in a single file which I will then automatically email to a specific email address.
How do I accomplish this?
Your current code selects a string from the file name, not the content.
To do what you are after, you can use simething like this:
$logFolder = '\\nas1\share1'
$outFile = Join-Path -Path $logFolder -ChildPath ('summary_{0:dd-MM-yyyy}.log' -f (Get-Date))
$captured = Get-ChildItem -Recurse -Path $logFolder -Include *.log | ForEach-Object {
($_ | Select-String -Pattern 'PC1','PC2','PC3' -SimpleMatch).Line
} | Sort-Object # sorting is optional of course
#output on screen
$captured
# output to new log file
$captured | Set-Content -Path $outFile
Next send this file as attachment like:
# use splatting for cmdlets that take a lot of parameters
$params = #{
SmtpServer = 'yourmailserver'
From = 'logtester#yourcompany.com'
To = 'someone#yourcompany.com'
Subject = 'Summary'
Body = 'Hi, in the attachment a summary of the backup logs'
Attachments = $outFile
# etc.
}
Send-Mailmessage #params
How big are your log files?
This will work but it loads the entire content of all files into memory and maybe inefficient with large file and/or a large number of files.
If there is a large number of files or large files you could do some nested foreach loops and loop through each computer name for each file.
This also uses match rather than like in where-object, you can use. like if you get false positives.
$FileContent = Get-Content -Path "\\nas1\share1\*.log"
$ComputerArray = 'PC1','PC2','PC3'
$DateStamp = get-date -Format dd-MM-yyyy
$OutFile = 'summary_' + $DateStamp + '.log'
foreach ($ComputerName in $ComputerArray)
{
$FileContent | Where {$_ -match $ComputerName} | Add-Content -Path $OutFile
}

Powershell Looping issue with process before next loop

I am new to Powershell and I am having an issue within a loop that I need assistance with. I am attempting to rename some files that are created as part of the process within the loop.
I have tested the code OUTSIDE of the loop and it works fine. However, when I try to put it in the loop, nothing seems to happen.
The files I need to rename are in the following locations…
(1)“\MYSERVER\MYCOMPANY\MYFOLDER\ MyPrintouts\EstimateImport\ImportPrintout.txt”
(2)“\MYSERVER\MYCOMPANY\MYFOLDER\ MyPrintouts\PostEntries\ImportPostEntries.txt”
I need to tack the date and time on the end. This code works for me OUTSIDE of the loop. I put it in a file I named RenameFiles.ps1
#File location
$ImportPrintout = “\\MYSERVER\MYCOMPANY\MYFOLDER\MyPrintouts\EstimateImport\ImportPrintout.txt”
$ImportPostEntries = “\MYSERVER\MYCOMPANY\MYFOLDER\ MyPrintouts\PostEntries\ImportPostEntries.txt”
#Find and rename the import printouts
Get-ChildItem $ImportPrintout -Filter "ImportPrintout.txt" | ForEach-Object {
Rename-Item $_.FullName "$BackupFolder$($_.BaseName -replace " ", "_" -replace '\..*?$')$(Get-Date -Format "MMddyyyy-HHmmss").txt"}
Get-ChildItem $ImportPostEntries -Filter "ImportPostEntires.txt" | ForEach-Object {
Rename-Item $_.FullName "$BackupFolder$($_.BaseName -replace " ", "_" -replace '\..*?$')$(Get-Date -Format "MMddyyyy-HHmmss").txt"}
This is how I added it to the loop as I want the files renamed BEFORE the next file is processed…
#Define actions after an Event is Detected
$action = {$files = Get-ChildItem -Path $watcher.Path -Filter $watcher.Filter #-Recurse
foreach ($file in $files)
{
#Define variables for log file
$changeType = $Event.SourceEventArgs.ChangeType #Your event trigger "Created"
$fileDate = $file.LastWriteTime #Date/Time Estimate was created
#logline contains = Date/Time of import, Created, Date/Time Created, filename
$logline = "$(Get-Date), $changeType, $fileDate, $file"
#Actions to take ==============================================================
#Write details to the log
Add-Content "“\\MYSERVER\MYCOMPANY\MYFOLDER\EstimateImportLog.txt" -value $logline
#Copy the estimate to the "ToBeProcessed" folder
Copy-Item $file.FullName -Destination $copyTo
#Move the estimate to the "EstimateHistory" folder
Move-Item $file.FullName -Destination $moveTo -Force
#Run the powershell script that launches the .bat file that launches the macro
Invoke-Expression "& '\\MYSERVER\MYCOMPANY\MYFOLDER\PSscriptToRunBATfile.ps1'"
#Pause the script for 30 seconds to allow the estimate to finish posting
Start-Sleep -Seconds 30
Invoke-Expression "& '“\\MYSERVER\MYCOMPANY\MYFOLDER\RenameFiles.ps1'"
}
}
This seems to “break” my loop. However, I need this to be done BEFORE going to the next file. How can I accomplish renaming these files before moving on. Any assistance would be greatly appreciated. Thank you!
As far as the loop failing, you're probably encountering an error. Either set your $ErrorActionPreference to Continue or set it to Stop and wrap try/catch blocks around your copy-item and move-item to detect and handle errors.
That probably also addresses the failure of the copy-item/move-item to change the file name, it's running into an error trying to perform that action and failing.

Variables not getting assigned in Powershell

I have a piece of code to search for all .txt and .csv files in the a bunch of submit folders inside a main folder.
#Folder to check for files
$path="\\Myfolder-DEV\RI*"
#variable which contains the number of files to be processed
$NumOfFiles = 0
Get-ChildItem $path -Recurse | % {
if (($_.Attributes -eq "Directory") -and ($_.FullName -match "submit")) {
$myPath = $_.FullName -replace "\\", "/"
Write-Host $myPath;
$fileEntries = Get-ChildItem -Path $_.FullName -include *.txt, *.csv -recurse;
foreach($fileName in $fileEntries){
$myDfile = $fileName.FullName -replace "\\", "/"
Write-Host $myDfile;
$myOfile = $myDfile -replace '\.[^.\\/]+$'
Write-Host $myOfile;
$NumOfFiles = $NumOfFiles + 1
}
}
}
echo "`n$NumOfFiles files were processed`n"
When I run this code in DEV, it works fine but when I do it in QA by modifying the path like this, it is not going through the folders at all.
$path="\\Myfolder-QA\RI*"
It even fails to write a value to the variable here
Write-Host $myPath;
I've tried modifying the scope - Clearing the variables at the end of the previous script. Restarting ISE but nothing seems to work.
Edit:
When I change the main directory to $path="\\Myfolder-QA\", it seems to work but it is searching through folders that I don't want it to. I need to search through only those folders which start with a RI and it doesnt seem to work. Any idea why it could happen?
Any help is appreciated
Thanks,
Sree

Powershell script to Get-Content of last line in multple files (loop array)

I have a folder with a list of files (*.log files) which all end with the following line, showing the output (different files will have different amounts) e.g.:
Total MB moved to Archive = 369.06444644928
I have the following query which will get the result of the first file in the folder:
Get-ChildItem $path| Where-Object {$_.Name} | Get-Content | select -last 1
I also have the following query which will output a list of all the log files:
Get-ChildItem $Path | ForEach {
Write-Host $_.Name}
I just want to create a loop to print a list of each last line of each log, not the log name or the last line of one log. And then the complex part, to pull the number part and add these together. Any tips?
If I'm reading the question correctly, this should work:
Get-ChildItem $Path |
ForEach {
Write-Host $_.Name
[double]$MB_Moved += (Get-Content $_.FullName -Tail 1) -replace '.+?([\d.]+)\s*','$1'
}

Powershell Remove Oldest Items

I am trying to use a do while loop to remove old files in multiple directories (starting from the oldest) until there is one left, at which point the program should end. The program should also only run if there is more than one file in the directory at runtime.
Here is my environment:
Top Folder
Folder 1
Folder 2
etc
In Folder 1, Folder 2, etc there should only be one file. The script should delete everything but the latest and nothing at all if there is only one file in there to begin with.
I have semi-accomplished this using the following code:
$basedir = "C:\Test"
Set-Location -Path C:\Test
$a = Get-ChildItem -recurse $basedir
if ($a.Count -gt 1) {
do
{
$a | Sort-Object LastWriteTime -Descending | Select-Object -Last 1 | remove-item
}
while
(
$a.Count -gt 1
)
}
It will only run when there is more than one file present, which is correct.
It correctly deletes the oldest file, but then it keeps on trying to delete the same file rather than rechecking the directory.
All I need help with at this point is getting it to re-run the loop once it has deleted a file, rather than trying to delete the same file over and over.
Thank you, sincerely, for any help and I apologise if this has been answered before. I did a lot of searching but couldn't find something with my situation.
Brad
To fix yours, you should re-define $a every time you pass through the loop, or just avoid using $a altogether. $a doesn't change just because the child items that were there when you created the object have.
Another way to do it would be to just select the last n-1 items in the folder.
#Get Items that aren't folders
$items = Get-ChildItems "c:\test" | ? {!($_.PSIsContainer)}
#Get a count - this is easier in PoSh 3
$itemCount = ($items | Measure-Object).Count
if ($itemCount -gt 1){
$items | Sort-Object LastWriteTime -Descending | Select-Object -Last ($itemCount - 1) | Remove-Item
}
To do this recursively is also fairly easy. First we get all of the folders we'll need to do the work in, then we'll move the code above into a foreach loop:
$folders = Get-ChildItems "C:\test" -recurse | ? {$_.PSIsContainer}
foreach ($folder in $folders) {
#Get Items that aren't folders
$items = Get-ChildItems $folder.FullName | ? {!($_.PSIsContainer)}
#Get a count - this is easier in PoSh 3
$itemCount = ($items | Measure-Object).Count
if ($itemCount -gt 1){
$items | Sort-Object LastWriteTime -Descending | Select-Object -Last ($itemCount - 1) | Remove-Item
}
}
You might need to run both scripts to prune the root as well, or add something like $folders += Get-Item "C:\test" before the foreach loop

Resources