Link to home
Start Free TrialLog in
Avatar of LarryDAH
LarryDAHFlag for United States of America

asked on

Powershell error

I am testing a powershell script that should check for the exit code of a task after it runs but the email it sends does not show that the task was successful even tho it shows that way on the server. Usually the error is unknown, but it varies.

I am using a script that I found at
stackoverflow.com/questions/15672388/robocopy-sending-email-attachment-appending-file-name

It runs robocopy fine but the email part is what is failing.

When I do a debug starting at the switch command (see the snip below) I get this error message and the Powershell IDE is putting a red tilde after ($LASTEXITCODE)

PS C:\scripts> Switch ($LASTEXITCODE)
Missing '{' in switch statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingCurlyBraceInSwitchStatement
PS C:\scripts>

Below is the first part of the code. The website has the entire thing.
<snip>
# Change robocopy options as needed. ( http://ss64.com/nt/robocopy.html )
Robocopy $SourceFolder $DestinationFolder /E /DCOPY:T /CopyAll /R:2 /W:2 /TEE /XA:SHTO /XJ /MT:16 /XF thumbs.db ~*.* /V /$LogFile /NP

Switch ($LASTEXITCODE)
16
{
{
$exit_code = "16"
$exit_reason = "***FATAL ERROR***"
#$IncludeAdmin = $False
#$SendEmail = $False
}
</snip>
Avatar of Qlemo
Qlemo
Flag of Germany image

And PS is correct about that. The 16 has to come between the two braces - review the code as posted originally.

Small rant: That PS code isn't very sophisticated - code repeated, unnecessary commands, missing indention and proper alignment ... . It works, however.
Avatar of LarryDAH

ASKER

Qlemo, if you have something simpler I would be happy to try it. I all want to do is run robocopy with Task Scheduler then get an email with the exit code. .
No, nothing (much) simpler. You have to check for all the error codes one by one ... But to show what I mean, I've incorporated your robocopy line already.
#*=============================================
#* Base variables
#*=============================================

$SourceFolder      = "C:\SourceFolder"
$DestinationFolder = "C:\DestinationFolder"
$Logfile           = "C:\Robocopy.log"
$Subject           = "Robocopy Results: Copy Purpose - Location to Location"
$SMTPServer        = "smtp.server.com"
$Sender            = "Server <email@address.com>"
$Recipients        = "User1 <email@address.com>"
$Admin             = "Admin <email@address.com>"
$SendEmail         = $True
$IncludeAdmin      = $True
$AsAttachment      = $False

#*=============================================
#* GMAIL variables
#*=============================================
#$SMTPServer = "smtp.gmail.com"
#$cred = New-Object System.Net.NetworkCredential("username", "password");
# Add "-UseSsl -Credential $cred" to the Send-MailMessage

#*=============================================
#* SCRIPT BODY
#*=============================================

# Change robocopy options as needed. ( http://ss64.com/nt/robocopy.html )
Robocopy $SourceFolder $DestinationFolder /E /DCOPY:T /CopyAll /R:2 /W:2 /TEE /XA:SHTO /XJ /MT:16 /XF thumbs.db ~*.* /V /$LogFile /NP
$exit_code = $LASTEXITCODE

# The following attempts to get the error code for Robocopy
# and use this as extra information and email determination.
Switch ($exit_code)
{
  16  { $exit_reason   = "***FATAL ERROR***"
        #$IncludeAdmin = $False
        #$SendEmail    = $False
        break
      }
   8  { $exit_reason   = "**FAILED COPIES**"
        #$IncludeAdmin = $False
        #$SendEmail    = $False
        break
      }
   4  { $exit_reason   = "*MISMATCHES*"
        $IncludeAdmin  = $False
        #$SendEmail    = $False
        break
      }
   2  { $exit_reason   = "EXTRA FILES"
        $IncludeAdmin  = $False
        #$SendEmail    = $False
        break
      }
   1  { $exit_reason   = "Copy Successful"
        $IncludeAdmin  = $False
        #$SendEmail    = $False
        break
      }
   0  { $exit_reason   = "No Change"
        $SendEmail     = $False
        $IncludeAdmin  = $False
        break
       }
  default {
        $exit_code    = "Unknown ($exit_code)"
        $exit_reason  = "Unknown Reason"
        #$SendEmail   = $False
        $IncludeAdmin = $False
      }
}

# Modify the subject with Exit Reason and Exit Code
$Subject += " : " + $exit_reason + " EC: " + $exit_code

# Defining a parameter hash table supplying all switches and values which are the same for all cases
$parms = @{
  From       = $Sender
  To         = $Recpients
  Subject    = $Subject
  DeliveryNotificationOption = 'onFailure'
  SmtpServer = $SMTPServer
}

# Test log file size to determine if it should be emailed
# or just a status email
if ((Get-ChildItem $Logfile).Length -lt 25mb)
{
  if ($IncludeAdmin) { $parms += @{CC = $Admin} }
  if ($AsAttachment) {
    $parms += @{ Body = "Robocopy results are attached."; Attachment = $Logfile }
  } Else {
    $parms += @{ Body = (Get-Content $LogFile | Out-String) }
  }
} Else {
  # Create the email body from the beginning and end of the $Logfile
  $parms += @{ Body = "Logfile was too large to send." + (Get-Content $LogFile -TotalCount 15 | Out-String) + (Get-Content $LogFile | Select-Object -Last 13 | Out-String) }
  # Include Admin if log file was too large to email
  $parms += @{ Cc = $Admin }
  #Exclude Admin if log file was too large to email
  #Send-MailMessage -From $Sender -To $Recipients -Subject $Subject -Body $Body -DeliveryNotificationOption onFailure -SmtpServer $SMTPServer
}
Send-MailMessage @parms

#*=============================================
#* END OF SCRIPT: Copy-RobocopyAndEmail.ps1
#*=============================================

Open in new window

Thanks for cleaning up the code. I cut the part about mailing me the log file. I can look at that if the job fails but not otherwise. I just ran from PowerShell and got my email with the subject below:

     Subject: Robo: CMF MTCC data to CA-6458-10 Z drive : Unknown Reason EC: Unknown (3)
     Body: If Exit Code (EC) is not 1 or 0 there may have been a problem

When I went to the server and ran the script by starting the scheduled task it shows that it completed successfully with Event ID 102 and Op Code 2, but the email subject was the same as above.      

So it looks like the ps1 file runs fine but is still not getting the exit code. If Event ID 02 means the job completed successfully is there some way to use that to trigger the email?

----------------------------------------------------------

The code that I ran:

#*=============================================
#* SCRIPT BODY
#*=============================================

# Change robocopy options as needed. ( http://ss64.com/nt/robocopy.html )
Robocopy $SourceFolder $DestinationFolder /E /DCOPY:T /CopyAll /R:2 /W:2 /TEE /XA:SHTO /XJ /MT:16 /XF thumbs.db ~*.* /V /$LogFile /NP
$exit_code = $LASTEXITCODE

# The following attempts to get the error code for Robocopy
# and use this as extra information and email determination.
Switch ($exit_code)
{
  16  { $exit_reason   = "***FATAL ERROR***"
        #$IncludeAdmin = $False
        #$SendEmail    = $False
        break
      }
   8  { $exit_reason   = "**FAILED COPIES**"
        #$IncludeAdmin = $False
        #$SendEmail    = $False
        break
      }
   4  { $exit_reason   = "*MISMATCHES*"
        $IncludeAdmin  = $False
        #$SendEmail    = $False
        break
      }
   2  { $exit_reason   = "EXTRA FILES"
        $IncludeAdmin  = $False
        #$SendEmail    = $False
        break
      }
   1  { $exit_reason   = "Copy Successful"
        $IncludeAdmin  = $False
        #$SendEmail    = $False
        break
      }
   0  { $exit_reason   = "No Change"
        $SendEmail     = $False
        $IncludeAdmin  = $False
        break
       }
  default {
        $exit_code    = "Unknown ($exit_code)"
        $exit_reason  = "Unknown Reason"
        #$SendEmail   = $False
        $IncludeAdmin = $False
      }
}

# Modify the subject with Exit Reason and Exit Code
$Subject += " : " + $exit_reason + " EC: " + $exit_code

# Create the email body from the beginning and end of the $Logfile
$Body = "If Exit Code (EC) is not 1 or 0 there may have been a problem" + (Get-Content $LogFile -TotalCount 15 | Out-String) + (Get-Content $Logfile | Select-Object -Last 13 | Out-String)
# Include Admin if log file was too large to email
Send-MailMessage -From $Sender -To $Recipients -Subject $Subject -Body $Body -DeliveryNotificationOption onFailure -SmtpServer $SMTPServer
#Exclude Admin if log file was too large to email
#Send-MailMessage -From $Sender -To $Recipients -Subject $Subject -Body $Body -DeliveryNotificationOption onFailure -SmtpServer $SMTPServer


#*=============================================
#* END OF SCRIPT: Copy-RobocopyAndEmail.ps1
#*=============================================
The opcode of the task doesn't need to be the same as the error code of RoboCopy. The task gets its return code from the shell, which runs perfect (hence = 2 - don't ask me why 2 is "ok").

As I suspected, RoboCopy return codes are bit mapped. The evaluation of return codes in the original script is nonsense, e.g. 3 is 2 + 1, "Extra Files" and "Copy Successful". That is usually ok, just some files found in the destination which are not in the source tree.
So, in fact we would have to see whether serious errors are reported, that are those 8 and higher. Or we just decode the bitmap. I've removed all the additional flags (IncludeAdmin, SendEmail), as you are not using it at the moment. I've also removed some other stuff, plus simplified the sending of parts of the log file beginning and end.
# For robocopy options see http://ss64.com/nt/robocopy.html
Robocopy $SourceFolder $DestinationFolder /E /DCOPY:T /CopyAll /R:2 /W:2 /TEE /XA:SHTO /XJ /MT:16 /XF thumbs.db ~*.* /V /$LogFile /NP
$exit_code = $LASTEXITCODE

# The following attempts to get the error code for Robocopy
# and use this as extra information and email determination.
# The code is a bitmap
16, 8, 4, 2, 1 | % {
  Switch ($exit_code -band $_)
  {
    16  { $exit_reason   += "***FATAL ERROR***, " ; break }
     8  { $exit_reason   += "**FAILED COPIES**, " ; break }
     4  { $exit_reason   += "*MISMATCHES*, "      ; break }
     2  { $exit_reason   += "EXTRA FILES, "       ; break }
     1  { $exit_reason   += "Copy Successful, "   ; break }
  }
}
$exit_code = $exit_code.Substring(0, $exit_code.Length-2)
if ($exit_code -eq 0) { $exit_reason = 'No Change' }

# Modify the subject with Exit Reason and Exit Code
$Subject += " : " + $exit_reason + " EC: " + $exit_code

# Create the email body from the beginning and end of the $Logfile
$Body = "If Exit Code (EC) is greater than 7 there is a problem " + (Get-Content $LogFile | Select -First 15 -Last 13)
Send-MailMessage -From $Sender -To $Recipients -Subject $Subject -Body $Body -DeliveryNotificationOption onFailure -SmtpServer $SMTPServer

Open in new window

Thanks, that script is much cleaner. When I ran it from the PS IDE it completes with this msg:

------------------------------------

Method invocation failed because [System.Int32] doesn't contain a method named 'Substring'.
At C:\scripts\Robocopy_data_mtcc.ps1:38 char:1
+ $exit_code = $exit_code.Substring(0, $exit_code.Length-2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

---------------------------------------

My email has the subject "EXTRA FILES,  EC: 2" and since that exit code is less than 7 I should be good to go, right?

If so, I have another question. I will be using this on 8-10 servers, not a lot, but when my mail server changes or the destination server is replaced I have to revisit each script and make the change. Is there a way to pull the $SMTPServer and $Destinationfolder from a centralized location?
ASKER CERTIFIED SOLUTION
Avatar of Qlemo
Qlemo
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Great help, thanks for working with me on this.