1

Can someone help me make it so that only one line of the email log is shown? something like this:

Bytes Copied: 27.0 k on Thursday, April 29, 2021 6:15:20 PM

This code give me this log:

$Logfile = "C:\Powershell\robocopy.txt"
Clear-Content "C:\Powershell\robocopy.txt" -Force
$EmailFrom = "testing@test.com"
$EmailTo = "test@test.com"
$EmailBody = "completed successfully. See attached log file"
$EmailSubject = "Summary"

$files = @("SCRIPT")

for($i = 0; $i -lt $files.Count; $i++){
    robocopy "C:\$($files[$i])" "C:\NEW TEST\folder\folder\$($files[$i])" /Z /e /xx /W:5 /MAXAGE:2 /NFL /NDL /NJH /nc /np /unilog+:$Logfile
}

Send-MailMessage -To $EmailTo -from $EmailFrom -Subject $EmailSubject -Body $EmailBody -attachment $Logfile -smtpserver 192.168.24 -Port 25


#log give me this:
------------------------------------------------------------------------------
               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :         4         0         4         0         0         0
   Files :        17         0        17         0         0         0
   Bytes :    27.0 k         0    27.0 k         0         0         0
   Times :   0:00:00   0:00:00                       0:00:00   0:00:00
   Ended : Thursday, April 29, 2021 11:55:52 AM
Steven
  • 6,817
  • 1
  • 14
  • 14
tricky69
  • 19
  • 6

2 Answers2

5

So assuming the log is just the summary data you can parse it pretty effectively:

$Log  = Get-Content C:\temp\robocopy.txt
$Date = ($Log[-1] -split ":", 2)[1].Trim()
$Line = $log | Where-Object{$_ -match "^\s+Bytes.+"}
$Line = $Line -split "\s+"
$Line = "Bytes Copied: {0} {1} on {2}" -f $Line[3], $Line[4], $Date

$Line

This should output:

Bytes Copied: 27.0 k on Thursday, April 29, 2021 11:55:52 AM

It can be inserted after you run your RoboCopy command. Then reference $Line in your email body or subject...

I didn't test your RoboCopy logging to ensure the format is as demonstrated. Nevertheless the concepts depicted here should serve the purpose well.

Explanation:

  • Getting the date by simply looking at the last line in the array returned by Get-Content. However, this could be done using a similar match strategy if for example, the [-1] approach isn't reliable.
  • To work out the byte count portion of the desired line. match the line, then split it up and refer to the elements we know hold the data.
  • Finally use the 2 extracted strings to format the desired string.

Note: Both concatenation and expanding strings are also options for the final assembly of the line. However, I thought the -f approach was more readable for this particular job.

Note: This keeps whatever multiplier symbol RoboCopy used, i.e. k, m or whatever. It would take more work and logic to calculate and display differently.

Note: It almost goes without saying there are probably a million ways to do this. If I refine at all I'll try to add more examples.

Another Approach:

Slightly different, use the -match Operator for both the date and the data quantity:

$Log  = Get-Content C:\temp\robocopy.txt
$Date = (($log -match "^\s+Ended.+") -split ":", 2)[1].Trim()
$Line = $Log -match "^\s+Bytes.+"
$Line = $Line -split "\s+"
$Line = "Bytes Copied: {0} {1} on {2}" -f $Line[3], $Line[4], $Date

$Line

This will work as long as $Log is an array, because -match will return the matches directly. As such we don't need the Where-Object clause.

Update:

It occurs to me that since you are running multiple RoboCopy jobs in a loop, and using the \Unilog+ parameter, you're probably faced with not only parsing out the data and data quantity but doing so across multiple runs and or with multiple such segments in the log file. Just want to address that as you have a few options.

  1. Since you're executing RoboCopy in loop and the $LogFile is known and constant with in that loop, you can add a separator line at the conclusion of each job.
for($i = 0; $i -lt $files.Count; $i++){
    $Log = robocopy "C:\$($files[$i])" "C:\NEW TEST\folder\folder\$($files[$i])" /Z /e /xx /W:5 /MAXAGE:2 /NFL /NDL /NJH /nc /np /unilog+:$Logfile /tee
    "---Job Completed---" | Add-Content $Logfile
}

I'm not going to demonstrate it, because there's amore convenient way, but this would give you a known string to split on in order to post-process the log file down to the strings you need.

  1. Add the /TEE parameter to your RoboCopy command and assign the output directly to a variable. This one I will demonstrate:
    $Logfile = "C:\Powershell\robocopy.txt"
    Clear-Content "C:\Powershell\robocopy.txt" -Force
    $EmailFrom = "testing@test.com"
    $EmailTo = "test@test.com"
    $EmailBody = [Collections.ArrayList]@("completed successfully. See attached log file & below summary","")
    $EmailSubject = "Summary"
    
    $files = @("SCRIPT")
    
    for($i = 0; $i -lt $files.Count; $i++){
        $Log = robocopy "C:\$($files[$i])" "C:\NEW TEST\folder\folder\$($files[$i])" /Z /e /xx /W:5 /MAXAGE:2 /NFL /NDL /NJH /nc /np /unilog+:$Logfile /tee
    
        $Date = (($log -match "^\s+Ended.+") -split ":", 2)[1].Trim()
        $Line = $Log -match "^\s+Bytes.+"
        $Line = $Line -split "\s+"
        $Line = "Bytes Copied: {0} {1} on {2}" -f $Line[3], $Line[4], $Date
    
        [Void]$EmailBody.Add($Line)
    }
    
    #Flip the $EmailBody array back to being a regular string.
    $EmailBody = $EmailBody -join "`r`n"
    
    Send-MailMessage -To $EmailTo -From $EmailFrom -Subject $EmailSubject -Body $EmailBody -Attachment $Logfile -SMTPserver 192.168.24.55

You can see $Log now captures the RoboCopy output directly, and you can execute the the remainder of the code looks similar, which saves us the need to post-process the log, as I mentioned in Options 1. Switching $EmailBody to an array list enabled us to easily append out calculated lines to an array. Convert the array back to a string so it's suitable for the -EmailBody parameter in the Send-MailMessage cmdlet.

Update For Comment:

You can change to show copied data rather than the total by simply advancing the array index we're referencing:

$Log  = Get-Content C:\temp\robocopy.txt
$Date = (($log -match "^\s+Ended.+") -split ":", 2)[1].Trim()
$Line = $Log -match "^\s+Bytes.+"
$Line = $Line -split "\s+"
$Line = "Bytes Copied: {0} {1} on {2}" -f $Line[5], $Line[4], $Date

$Line

HOWEVER, notice that I'm still referencing $Line[4]. That's because RoboCopy isn't appending a multiplier symbol like "k" when there's 0 bytes, so we might as well take the symbol that goes with the total. That said, if the total is also 0, it may throw off the array indices, we can of course accommodate it with further work, but you'd have to decide the likelihood of that scenario.

Note: I haven't yet changed earlier examples. When the QA is settled, I'll try to reword so it's consistent. Considering there've been a number of updates.

Update for Comment Requesting Csv Export

The *-Csv cmdlets convert objects to comma separated values (strings), using the object properties as column headers and write that to file. So a subsequent example that provides for previous email body requirement and does the Csv export might look something like:

$Logfile = "C:\PowerShell\robocopy.txt"
$CsvFile = "C:\PowerShell\RoboCsv.txt"
Clear-Content "C:\Powershell\robocopy.txt" -Force
$EmailFrom = "testing@test.com"
$EmailTo = "test@test.com"
$EmailBody = [Collections.ArrayList]@("completed successfully. See attached log file & below summary","")
$EmailSubject = "Summary"
$CsvData      = [Collections.ArrayList]@()

$files = @("SCRIPT")

for($i = 0; $i -lt $files.Count; $i++){
    $Source = "C:\$($files[$i])"
    $Dest   = "C:\NEW TEST\folder\folder\$($files[$i])"
    
    $Log = robocopy $Source $Dest /Z /e /xx /W:5 /MAXAGE:2 /NFL /NDL /NJH /nc /np /unilog+:$Logfile /tee

    $Date = (($log -match "^\s+Ended.+") -split ":", 2)[1].Trim()
    $Line = $Log -match "^\s+Bytes.+"
    $Line = $Line -split "\s+"
    
    $Copy = $Line[5]
    $Mult = $Line[4]

    $Line = "Bytes Copied: $Copy $Mult on $Date"

    [Void]$EmailBody.Add($Line)

    # For Csv output:
    [Void]$CsvData.Add(
        [PSCustomObject]@{
            SizeCopied  = $Copy
            Date        = $Date
            Source      = $Source
            Destination = $Dest
        } )
}

#Flip the $EmailBody array back to being a regular string.
$EmailBody = $EmailBody -join "`r`n"

Send-MailMessage -To $EmailTo -From $EmailFrom -Subject $EmailSubject -Body $EmailBody -Attachment $Logfile -SMTPserver 192.168.24.55

#Output to CSVFile
$CsvData | Export-Csv -Path $CsvFile -NoTypeInformation

What's added is an array of custom objects using values we previously calculated. Obviously some new variables were defined because we have to reuse/reference to do 2 things instead of 1.

Please note if I were doing this from scratch and had a better handle on the requirements. I might structure it differently. A few things that come to mind:

  1. Use a different loop construct. It doesn't look like the traditional loop is technically needed, and the code may be more readable with one of the ForEach constructs.
  2. Only populate the the object array in the loop. Then post process the array to extract the email body data you need as well as the Export-Csv command. This may also allow you to completely avoid the array incrementation issue and thus not bother with [Collections.ArrayList].

An Aside:

  • You don't need to specify -Port 25 that's the default.
  • You are missing the last octet of your SMTP servers IP address, for which I just threw in "55", but you'll want to fix that.
Steven
  • 6,817
  • 1
  • 14
  • 14
  • Can it show whats copied instead of the total bytes? like showing the second column where its says copied and not the total? thanks so much! – tricky69 Apr 30 '21 at 18:01
  • 1
    I knew that question was coming, I just added another update to address it. – Steven Apr 30 '21 at 18:11
  • Hi sorry to be asking so much. But can this work on a .csv file? Like a column for size copied, date, source, and destination? This would help me so much. I appreciate your time in helping me! – tricky69 Apr 30 '21 at 18:30
  • You want to output to csv? – Steven Apr 30 '21 at 18:38
  • yes if that possible. I really appreciate your help. – tricky69 Apr 30 '21 at 18:42
  • OK I added something workable. Note: I haven't tested it, but it should be enough to get you there. If you don't mind, please accept the answer, it will help others find it. Moreover, we're way beyond the scope of the original question. – Steven Apr 30 '21 at 19:00
  • Im getting these errors :( You cannot call a method on a null-valued expression. At line:29 char:5 + $Date = (($log -match "^\s+Ended.*") -Split ":", 2)[1].Trim() + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull You cannot call a method on a null-valued expression. At line:41 char:5 + [Void]$CsvExportFile.Add( + ~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull – tricky69 Apr 30 '21 at 19:18
  • I’m not at the pc at the moment. However that’s probably coming from the .trim() call and would mean earlier statements didn’t return expected data. – Steven Apr 30 '21 at 19:29
  • I'll try to figure it out :/ But if you can take a look as well I would be nice. I really do appreciate you helping me out. – tricky69 Apr 30 '21 at 19:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231804/discussion-between-steven-and-tricky69). – Steven Apr 30 '21 at 20:00
2

Obviously @Steven's answer is way better and more in-depth than mine, but I thought I'd share this ugly piece of code anyway:

$Log = "C:\temp\robocopy.txt"
$Bytes = (Get-Content $Log | Where-Object {$_ -like "*bytes*"} | Select -First 1).split(':')[1].trim(' ').split(' ')[0]
$Body = "Bytes Copied: $($Bytes)k on $(Get-Date)"
Send-MailMessage -To $EmailTo -from $EmailFrom -Subject $EmailSubject -Body $Body -attachment $Logfile -smtpserver 192.168.24 -Port 25

Or put it into a file to attach. It may be ugly code, but it gets the information you're after.

Lars Panzerbjrn
  • 196
  • 3
  • 12
  • 1
    Thanks for sharing. It's often the case that someone answers while you were already working on something, so I always feel like; might as well... Especially if there difference in approach to solving a problem. – Steven Apr 30 '21 at 23:10