Powershell - sending email with multiple attachments

Leoš Junek 21 Reputation points
2020-11-26T18:43:18.697+00:00

Hello,

in my Powershell script some logs are detected to be wrong, hence I want them to be sent via email. Every time the script runs the list of logs can be different, so I store the log names in an array. Then I create a long comma separated string with file names and paste the output string after "-Attachment" when sending an email.

However Powershell detects the comma separated string as a single filename / filepath and throws an error. I could overcome the problem by writing single filenames separated with comma, but it does not fit in my script because filenames and number of files can be different every run of the script.

Here is my code, rn ... relative name (path), fn ... full name (path)

        $rn1="imp_qn03.log"
        $rn2="imp_ssp1.log"
        $rn3="imp_ssw2.log"
        $rn4="imp_t2ns01.log"
        $rn_all=$rn1+','+$rn2+','+$rn3+','+$rn4
        $base_dir='D:\TaskScheduler\PadrSQL\ZMENY'
        $fn1=$base_dir+'\'+$rn1
        $fn2=$base_dir+'\'+$rn2
        $fn3=$base_dir+'\'+$rn3
        $fn4=$base_dir+'\'+$rn4
        $fn_all=$fn1+','+$fn2+','+$fn3+','+$fn4
        Write-Debug "rn_all=$rn_all"
        Write-Debug "fn_all=$fn_all"

        # Email 1 
        Send-MailMessage -To $to -From $from -Subject "more variables after Attachment rn1..rn4" -Body $body -SmtpServer $smtp_server -Port 25 -BodyAsHtml -Priority Normal -Attachments $rn1,$rn2,$rn3,$rn4
        # Email 2 
        Send-MailMessage -To $to -From $from -Subject "single variable after Attachment rn_all" -Body $body -SmtpServer $smtp_server -Port 25 -BodyAsHtml -Priority Normal -Attachments $rn_all
        # Email 3
        Send-MailMessage -To $to -From $from -Subject "more variables after Attachment fn1..fn4" -Body $body -SmtpServer $smtp_server -Port 25 -BodyAsHtml -Priority Normal -Attachments $fn1,$fn2,$fn3,$fn4
        # Email 4
        Send-MailMessage -To $to -From $from -Subject "single variable after Attachment fn_all" -Body $body -SmtpServer $smtp_server -Port 25 -BodyAsHtml -Priority Normal -Attachments $fn_all

        # debug Error email 2 with DEBUG info
        DEBUG: rn_all=imp_qn03.log,imp_ssp1.log,imp_ssw2.log,imp_t2ns01.log
        DEBUG: fn_all=D:\TaskScheduler\PadrSQL\ZMENY\imp_qn03.log,D:\TaskScheduler\PadrSQL\ZMENY\imp_ssp1.log,D:\TaskScheduler\PadrSQL\ZMENY\imp_ssw2.log,D:\TaskScheduler\PadrSQL\ZMENY\imp_t2ns01.log
        Send-MailMessage : Could not find file 'D:\TaskScheduler\PadrSQL\ZMENY\imp_qn03.log,imp_ssp1.log,imp_ssw2.log,imp_t2ns01.log'.
        At D:\TaskScheduler\PadrSQL\kontrola_logu1.ps1:210 char:5
        +     Send-MailMessage -To $to -From $from -Subject "single variable af ...
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            + CategoryInfo          : NotSpecified: (:) [Send-MailMessage], FileNotFoundException
            + FullyQualifiedErrorId : System.IO.FileNotFoundException,Microsoft.PowerShell.Commands.SendMailMessage

        # Error email 4 with DEBUG info
        DEBUG: rn_all=imp_qn03.log,imp_ssp1.log,imp_ssw2.log,imp_t2ns01.log
        DEBUG: fn_all=D:\TaskScheduler\PadrSQL\ZMENY\imp_qn03.log,D:\TaskScheduler\PadrSQL\ZMENY\imp_ssp1.log,D:\TaskScheduler\PadrSQL\ZMENY\imp_ssw2.log,D:\TaskScheduler\PadrSQL\ZMENY\imp_t2ns01.log
        Send-MailMessage : The given path's format is not supported.
        At D:\TaskScheduler\PadrSQL\kontrola_logu1.ps1:212 char:5
        +     Send-MailMessage -To $to -From $from -Subject "single variable af ...
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            + CategoryInfo          : NotSpecified: (:) [Send-MailMessage], NotSupportedException
            + FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.SendMailMessage

Email 1 and Email 3 were sent and delivered without problems.

Email 2 error message reveals PowerShell prefixed the whole string with current directory

Email 4 error message does not reveal the problem, it only complains about wrong path format.

Update 1:
Though I mentioned array in the text above, it was not used in mentioned code block, because the root of the problem could be shown with string variables rn1... rn4, fn1...fn4. In my original script I select wrong logs with [Regex]::Matches into variable $rn_files. It is a list of filenames separated by space. Then I create an array $rn_array from $rn_files with Split. Then I form variable $attachment by concatenating array items together with comma as delimiter. However variable $attachment cannot be used after -Attachments when sending emails.

  $rn_files=[Regex]::Matches((Select-String -Path *.log -Pattern $regex),'(imp_[A-Z a-z 0-9 _]*.log):[0-9]*:[0-3][0-9].[0-1][0-9].20[2-9][0-9]-[0-9]*:[0-5][0-9]:[0-5][0-9] import with errors')|ForEach-Object {$_.Groups[1].Value}
  $rn_array=$rn_files.Split(" ")
  $attachment=$rn_array -join ','
  ...
  Send-MailMessage -To ... -Attachments $attachment #throws an error

My goal is to find a way how to use $rn_files or its derivates (e.g. array $rn_array) to send multiple logs as an attachment.

Update 2
Passing filenames with pipe does not solve the problem.

    # No error
    $rn1,$rn2 | Send-MailMessage -To $to -From $from -Subject "more variables after Attachment rn1..rn2" -Body $body-SmtpServer $smtp_server -Port 25 -BodyAsHtml -Priority Normal

    # An error is thrown
    $rn_all | Send-MailMessage -To $to -From $from -Subject "single variable after Attachment rn_all" -Body $body -SmtpServer $smtp_server -Port 25 -BodyAsHtml -Priority Normal

    # Error content  
    Send-MailMessage : Could not find file 'D:\TaskScheduler\PadrSQL\ZMENY\imp_qn03.log,imp_ssp1.log,imp_ssw2.log,imp_t2ns01.log'.
    At D:\TaskScheduler\PadrSQL\kontrola_logu.ps1:210 char:22
     +     [array]$rn_all | Send-MailMessage -To $to -From $from -Subject "s ...
     +    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Send-MailMessage], FileNotFoundException
        + FullyQualifiedErrorId : System.IO.FileNotFoundException,Microsoft.PowerShell.Commands.SendMailMessage

Solution
Send-MailMessage accepts array of strings, not a single string. Here is a functional code:

  # Get a single string of filenames separated with space
  $rn_files=[Regex]::Matches((Select-String -Path *.log -Pattern $regex),'(imp_[A-Z a-z 0-9 _]*.log):[0-9]*:[0-3][0-9].[0-1][0-9].20[2-9][0-9]-[0-9]*:[0-5][0-9]:[0-5][0-9] import with errors')|ForEach-Object {$_.Groups[1].Value}

  # Create an array of strings using space as delimiter for array items - each item of array contains one filename 
  $rn_array=$rn_files.Split(" ")

  # Send email with attachments
  Send-MailMessage ... -Attachments $rn_array

Leos

Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,520 questions
0 comments No comments
{count} votes

Accepted answer
  1. Andreas Baumgarten 108.7K Reputation points MVP
    2020-11-26T19:10:48.333+00:00

    In your script the variable $rn_all is a string variable not an array variable
    You can verify this: $rn_all.GetType()

    You can try to create an array variable instead:

    $rn1="imp_qn03.log"
    $rn2="imp_ssp1.log"
    $rn3="imp_ssw2.log"
    $rn4="imp_t2ns01.log"
    [array]$rn_all=$rn1,$rn2,$rn3,$rn4
    

    Same for the $fn_all:

    $fn1=$base_dir+'\'+$rn1
    $fn2=$base_dir+'\'+$rn2
    $fn3=$base_dir+'\'+$rn3
    $fn4=$base_dir+'\'+$rn4
    [array]$fn_all=$fn1,$fn2,$fn3,$fn4
    

    Maybe this is helpful.


    (If the reply was helpful please don't forget to upvote and/or accept as answer, thank you)

    Regards
    Andreas Baumgarten

    1 person found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. Rich Matheisen 46,636 Reputation points
    2020-11-26T20:25:13.06+00:00

    See if this works any better for you:

    $rn1="imp_qn03.log"
    $rn2="imp_ssp1.log"
    $rn3="imp_ssw2.log"
    $rn4="imp_t2ns01.log"
    $rn_all = $rn1, $rn2, $rn3, $rn4
    #Or, if you don't need $rn1..$rn4, why not this:
    #$rn_all = "imp_qn03.log", "imp_ssp1.log", "imp_ssw2.log", "imp_t2ns01.log"
    
    $base_dir='D:\TaskScheduler\PadrSQL\ZMENY'
    $fn1= Join-Path $base_dir $rn1
    $fn2= Join-Path $base_dir $rn2
    $fn3= Join-Path $base_dir $rn3
    $fn4= Join-Path $base_dir $rn4
    $fn_all=$fn1, $fn2, $fn3, $fn4
    #Or, if you don't need $fn1..$fn4, why not this:
    #$fn_all = $rn_all | ForEach-Object{Join-Path $base_dir $_}
    
    Write-Debug "rn_all=$rn_all"
    Write-Debug "fn_all=$fn_all"
    
    # Use "splatting" to supply long series of parameters
    $Params=@{
        To = $to
        From = $from
        Body = $body
        SmtpServer = $smtp_server
        Port = 25
        BodyAsHtml = $true
        Prority = Normal
        Subject = ""            # filled in later
        Attachments = ""        # filled in later
    }
    # Email 1
    $Params.Subject = "more variables after Attachment rn1..rn4"
    $Params.Attachments = $rn1,$rn2,$rn3,$rn4                       # same as $rn_all
    Send-MailMessage @Params
    # Email 2 
    $Params.Subject = "single variable after Attachment rn_all"
    $Params.Attachments = $rn_all
    Send-MailMessage @Params
    # Email 3
    $Params.Subject = "more variables after Attachment fn1..fn4"
    $Params.Attachments = $fn1,$fn2,$fn3,$fn4                       # same as $fn_all
    Send-MailMessage @Params
    # Email 4
    $Params.Subject = "single variable after Attachment fn_all"
    $Params.Attachments = $fn_all
    Send-MailMessage @Params
    

    In addition to creating the variables properly, you can make the code easier to read by using "splatting". I added a few lines of code (they're commented) to neaten things, too.

    0 comments No comments

  2. Andreas Baumgarten 108.7K Reputation points MVP
    2020-11-26T20:25:50.243+00:00

    Does this $attachment string work?

    $fn1=$base_dir+'\'+$rn1
    $fn2=$base_dir+'\'+$rn2
    $fn3=$base_dir+'\'+$rn3
    $fn4=$base_dir+'\'+$rn4
    [array]$fn_all="$fn1","$fn2","$fn3","$fn4"
    $attachment = '"{0}"' -f ($fn_all -join '","')
    $attachment
    

    The result will look like this; "C:\test\imp_qn03.log","C:\test\imp_ssp1.log","C:\test\imp_ssw2.log","C:\test\imp_t2ns01.log"


    (If the reply was helpful please don't forget to upvote and/or accept as answer, thank you)

    Regards
    Andreas Baumgarten

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.