user12532474
user12532474

Reputation:

Prevent output from writing multiple times in Powershell script

I am trying to output $message in case a file or multiple files in a folder match today's or yesterday's date. In this case, if the file matches the dates, it should output a message for each file, for example, "file $filename matches", where $filename is the name of the file that matched the date.

On the other hand if a file doesn't match the current date or yesterday's date it will also return a message, but only one message with the filenames which doesn't match the date.

That is not happening with the following code:

    $in = $false
    foreach ($file in Get-ChildItem -Path $Directory) {
        $today = $file.LastWriteTime.Date -eq [datetime]::Today
        $yesterday = $file.LastWriteTime.Date -eq [datetime]::Today.AddDays(-1)
        
        if ($today -or $yesterday) {
            $message = "file exists"
            $in = $true
            Write-Output $message
        }
    }
    

    if ($in -eq $false) {
        $message_error = "file does not exist" 
        Write-Output $message_error
    }

Instead when a file matches the dates (yesterday's or today's date) it outputs the message multiple times, more than 20x if indented like above.

As for if it doesn't match, it is outputting only 1x but it should be 2x because there are 2 files that don't match the dates.

Can anyone give me an hint?


Output should look like

If file or files in folder matches date:

"File example.txt matches date
and
File example2.txt matches date"

Else if files in folder don't match date:

"File example.txt, example2.txt don't match dates"

EDIT

Instead I am trying like this:

 $in = $false
 foreach ($file in Get-ChildItem -Path $Directory) {
    $today = $file.LastWriteTime.Date -eq [datetime]::Today
    $yesterday = $file.LastWriteTime.Date -eq [datetime]::Today.AddDays(-1)
                
    if ($today -or $yesterday) {
        $msg_found = "file" + $file "exists" 
        $in = $true
               
    } else {
        $msg_notfound = "File" + $file + "Not found"
            
    }
      
 Write-Output $msg_notfound
 Write-Output $msg_found

Here the messages are outputted in the right amount but they appear both in any circunstance. Would it solve if I use an if loop?

Upvotes: 3

Views: 511

Answers (5)

Theo
Theo

Reputation: 61123

Simply collect the dates that do not match the reference date in a variable and write the files that do match out directly to the console:

# set the reference date to midnight using .Date
$refDate = [datetime]::Today.AddDays(-1).Date
# loop through the files in the directory and test their LastWriteTime dates
$notMatch = foreach ($file in (Get-ChildItem -Path $Directory -File)) {
    if ($file.LastWriteTime -ge $refDate) { 
        # use Write-Host so this message doesn't end up in variable $notMatch
        Write-Host "File '$($file.Name)' matches the date"
    }
    else {
        # output the filename so it gets collected in variable $notMatch
        $file.Name
    }
}

# now output the list of files that didn't match to the console
if (@($notMatch).Count) {
    $message = "Files that don't match the date:`r`n{0}" -f ($notMatch -join [environment]::NewLine)
    Write-Host $message -ForegroundColor Red
}
else {
    Write-Host "All files matched the date!" -ForegroundColor Green
}

Since you asked, here's two alternative codes for you

1: Capture everything in an array variable and split out later

# set the reference date to midnight using .Date
$refDate = [datetime]::Today.AddDays(-1).Date
# loop through the files in the directory and test their LastWriteTime dates
$result = foreach ($file in (Get-ChildItem -Path $Directory -File)) {
    [PsCustomObject]@{
        File        = $file.Name
        MatchesDate = ($file.LastWriteTime -ge $refDate)  # --> either $true or $false
    }
}

# you now have a list of every file name found in the directory, together with a boolean
# value teling you if the LastWriteTime matched the $refDate or not.

# split out for console output
$matchedFiles = ($result | Where-Object { $_.MatchesDate }).File -join [environment]::NewLine
Write-Host "Files that were last modified yesterday or today:`r`n{0}" -f $matchedFiles -ForegroundColor Green

$notMatch = ($result | Where-Object {-not $_.MatchesDate }).File -join [environment]::NewLine
Write-Host "Files that were last modified before yesterday:`r`n{0}" -f $notMatch -ForegroundColor Red

2: Capture everything in two separate lists

# set the reference date to midnight using .Date
$refDate = [datetime]::Today.AddDays(-1).Date

# create two lists
$matchedFiles = [System.Collections.Generic.List[string]]::new()
$notMatch     = [System.Collections.Generic.List[string]]::new()

# loop through the files in the directory and test their LastWriteTime dates
# we're not outputting anything inside the list, but add to the appropriate list instead
foreach ($file in (Get-ChildItem -Path $Directory -File)) {
    if ($file.LastWriteTime -ge $refDate) { 
        $matchedFiles.Add($file.Name)
    }
    else {
        $notMatch.Add($file.Name)
    }
}

# you now have separate lists of every iles that matched and files that did not match the date criterium

# console output
Write-Host "Files that were last modified yesterday or today:`r`n{0}" -f ($matchedFiles -join [environment]::NewLine) -ForegroundColor Green
Write-Host "Files that were last modified before yesterday:`r`n{0}" -f ($notMatch -join [environment]::NewLine) -ForegroundColor Red

Upvotes: 1

Santiago Squarzon
Santiago Squarzon

Reputation: 60220

An alternative to the already proposed answers using the .Where() method combined with the Split mode:

$directory = '.'
$targetDate = [datetime]::Today.AddDays(-1)

$Match, $noMatch = (Get-ChildItem -Path $directory -File).Where({
    $_.LastWriteTime -ge $targetDate
}, 'Split')

if($Match)
{
    '=' * 60
    [string]::Format(
        '{0} file{1} found with Date greater than or equal to {2}',
        $Match.Count,
        ($null, 's')[[int]$Match.Count -gt 1],
        $targetDate.ToShortDateString()
    )
    '=' * 60
    ''
    $Match.ForEach({
        '- File: {0} with LastWriteTime {1}' -f $_.Name, $_.LastWriteTime
    })
}
else
{
    '=' * 60
    [string]::Format(
        'No files found with Date greater than or equal to {0}',
        $targetDate.ToShortDateString()
    )
    '=' * 60
    $noMatch | Format-Wide Name -AutoSize
}

Upvotes: 0

Josh Gattis
Josh Gattis

Reputation: 91

Try this:

 foreach ($file in Get-ChildItem -Path $Directory) {
    $today = $file.LastWriteTime.Date -eq [datetime]::Today
    $yesterday = $file.LastWriteTime.Date -eq [datetime]::Today.AddDays(-1)
    
    # If file was written to today or yesterday, write $msg_found           
    if ($today -or $yesterday) {
        $msg_found = "File " + $file + " exists"
        Write-Output $msg_found         
    }
    
    # If not, write $msg_notfound 
    else {
        $msg_notfound = "File " + $file + " not found"
        Write-Output $msg_notfound
            
    }
}

If I'm understanding correctly, the reason why it was writing both the $msg_found and $msg_notfound variables is because you had them outside of the If/Else statement.

Keeping them inside the If/Else will make it so it only writes the $msg within the If or the Else.

Edit: Above, I removed the $in variable, but I wasn't sure if you needed that. So I modified the script you posted before the edit to do the same thing as the script I put originally in case you do.

I left comments to tell you what I changed, but let me know if you have any questions:

$in = $false
foreach ($file in Get-ChildItem -Path $Directory) {
    $today = $file.LastWriteTime.Date -eq [datetime]::Today
    $yesterday = $file.LastWriteTime.Date -eq [datetime]::Today.AddDays(-1)
    
    # If file was made today or yesterday, $in is true, write $message 
    if ($today -or $yesterday) {
        $message = "$File exists"
        $in = $true
        Write-Output $message
    }
    
    # If not, $in is false for this file in the ForEach loop
    else{
        $in = $false
    }
    
    # Moved the second If statement into the ForEach loop 
    # If the If/Else set $in as false for this file, write $message_error
    if ($in -eq $false) {
    $message_error = "$File does not exist" 
    Write-Output $message_error
    }
}

Upvotes: 0

A. Lion
A. Lion

Reputation: 680

$Directory="T:\temp2"
$in = $false
$message=""
foreach ($file in Get-ChildItem -Path $Directory) {
    $today = $file.LastWriteTime.Date -eq [datetime]::Today
    $yesterday = $file.LastWriteTime.Date -eq [datetime]::Today.AddDays(-1)
    
    if ($today -or $yesterday) {
        $in = $true
        Write-Output ("File " + $file.Name +" matches date")
    } else {
        $message +=(" "+$file.Name+",")
    }
}


if ($in -eq $false) {
    $message_error= ("File" + $message.Substring(0,$message.Length-1) + " don't matches date")
    Write-Output $message_error
}

Upvotes: 0

Abraham Zinala
Abraham Zinala

Reputation: 4694

continuing from my comments... if you're looking to output the names of the files that are greater than or equal to, yesterdays date, there's a couple of ways you can go about this:

Get-ChildItem -Path $Directory | 
    ForEach-Object -Begin {
        $notMatch = [System.Collections.ArrayList]::new()
        $date     = [datetime]::Today.AddDays(-1)
    } -Process {
        if ($_.LastWriteTime -ge $date) {
            Write-Host -Object  "File: [$($_.Name)] - Matches Date"
        }
        else {
            $null = $notMatch.Add($_.Name)
        }
    } -End {
        ($notMatch -join ', ' ) + "don't match dates."
    }

Fortunately, matching the dates as they come is an easy solution for the files that are a match. As for the ones that aren't, you can create an array to hold the values of the name until the end of your loop to display the values concatenated by a (", ").

Upvotes: 1

Related Questions