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.
- 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.
- 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:
- 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.
- 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.