0

can someone please help me with changing the below code to not take pictures from the email body (signatures, logos, etc.). and only takes the attachment from outlook email itself. I am using the default "mAttachmentSaver" script from Microsoft.

Attribute VB_Name = "mAttachmentSaver"
'---------------------------------------------------------------------------------
' The sample scripts are not supported under any Microsoft standard support
' program or service. The sample scripts are provided AS IS without warranty
' of any kind. Microsoft further disclaims all implied warranties including,
' without limitation, any implied warranties of merchantability or of fitness for
' a particular purpose. The entire risk arising out of the use or performance of
' the sample scripts and documentation remains with you. In no event shall
' Microsoft, its authors, or anyone else involved in the creation, production, or
' delivery of the scripts be liable for any damages whatsoever (including,
' without limitation, damages for loss of business profits, business interruption,
' loss of business information, or other pecuniary loss) arising out of the use
' of or inability to use the sample scripts or documentation, even if Microsoft
' has been advised of the possibility of such damages.
'---------------------------------------------------------------------------------

Option Explicit

' *****************
' For Outlook 2010.
' *****************
#If VBA7 Then
    ' The window handle of Outlook.
    Private lHwnd As LongPtr

    ' /* API declarations. */
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, _
        ByVal lpWindowName As String) As LongPtr

' *****************************************
' For the previous version of Outlook 2010.
' *****************************************
#Else
    ' The window handle of Outlook.
    Private lHwnd As Long

    ' /* API declarations. */
    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
#End If

' The class name of Outlook window.
Private Const olAppCLSN As String = "rctrl_renwnd32"
' Windows desktop - the virtual folder that is the root of the namespace.
Private Const CSIDL_DESKTOP = &H0
' Only return file system directories. If the user selects folders that are not part of the file system, the OK button is grayed.
Private Const BIF_RETURNONLYFSDIRS = &H1
' Do not include network folders below the domain level in the dialog box's tree view control.
Private Const BIF_DONTGOBELOWDOMAIN = &H2
' The maximum length for a path is 260 characters.
Private Const MAX_PATH = 260

' ######################################################
'  Returns the number of attachements in the selection.
' ######################################################
Public Function SaveAttachmentsFromSelection() As Long
    Dim objFSO              As Object       ' Computer's file system object.
    Dim objShell            As Object       ' Windows Shell application object.
    Dim objFolder           As Object       ' The selected folder object from Browse for Folder dialog box.
    Dim objItem             As Object       ' A specific member of a Collection object either by position or by key.
    Dim selItems            As Selection    ' A collection of Outlook item objects in a folder.
    Dim atmt                As Attachment   ' A document or link to a document contained in an Outlook item.
    Dim strAtmtPath         As String       ' The full saving path of the attachment.
    Dim strAtmtFullName     As String       ' The full name of an attachment.
    Dim strAtmtName(1)      As String       ' strAtmtName(0): to save the name; strAtmtName(1): to save the file extension. They are separated by dot of an attachment file name.
    Dim strAtmtNameTemp     As String       ' To save a temporary attachment file name.
    Dim intDotPosition      As Integer      ' The dot position in an attachment name.
    Dim atmts               As Attachments  ' A set of Attachment objects that represent the attachments in an Outlook item.
    Dim lCountEachItem      As Long         ' The number of attachments in each Outlook item.
    Dim lCountAllItems      As Long         ' The number of attachments in all Outlook items.
    Dim strFolderPath       As String       ' The selected folder path.
    Dim blnIsEnd            As Boolean      ' End all code execution.
    Dim blnIsSave           As Boolean      ' Consider if it is need to save.

    blnIsEnd = False
    blnIsSave = False
    lCountAllItems = 0

    On Error Resume Next

    Set selItems = ActiveExplorer.Selection

    If Err.Number = 0 Then

        ' Get the handle of Outlook window.
        lHwnd = FindWindow(olAppCLSN, vbNullString)

        If lHwnd <> 0 Then

            ' /* Create a Shell application object to pop-up BrowseForFolder dialog box. */
            Set objShell = CreateObject("Shell.Application")
            Set objFSO = CreateObject("Scripting.FileSystemObject")
            Set objFolder = objShell.BrowseForFolder(lHwnd, "Select folder to save attachments:", _
                                                     BIF_RETURNONLYFSDIRS + BIF_DONTGOBELOWDOMAIN, CSIDL_DESKTOP)

            ' /* Failed to create the Shell application. */
            If Err.Number <> 0 Then
                MsgBox "Run-time error '" & CStr(Err.Number) & " (0x" & CStr(Hex(Err.Number)) & ")':" & vbNewLine & _
                       Err.Description & ".", vbCritical, "Error from Attachment Saver"
                blnIsEnd = True
                GoTo PROC_EXIT
            End If

            If objFolder Is Nothing Then
                strFolderPath = ""
                blnIsEnd = True
                GoTo PROC_EXIT
            Else
                strFolderPath = CGPath(objFolder.Self.Path)

                ' /* Go through each item in the selection. */
                For Each objItem In selItems
                    lCountEachItem = objItem.Attachments.Count

                    ' /* If the current item contains attachments. */
                    If lCountEachItem > 0 Then
                        Set atmts = objItem.Attachments

                        ' /* Go through each attachment in the current item. */
                        For Each atmt In atmts

                            ' Get the full name of the current attachment.
                            strAtmtFullName = atmt.FileName

                            ' Find the dot postion in atmtFullName.
                            intDotPosition = InStrRev(strAtmtFullName, ".")

                            ' Get the name.
                            strAtmtName(0) = Left$(strAtmtFullName, intDotPosition - 1)
                            ' Get the file extension.
                            strAtmtName(1) = Right$(strAtmtFullName, Len(strAtmtFullName) - intDotPosition)
                            ' Get the full saving path of the current attachment.
                            strAtmtPath = strFolderPath & atmt.FileName

                            ' /* If the length of the saving path is not larger than 260 characters.*/
                            If Len(strAtmtPath) <= MAX_PATH Then
                                ' True: This attachment can be saved.
                                blnIsSave = True

                                ' /* Loop until getting the file name which does not exist in the folder. */
                                Do While objFSO.FileExists(strAtmtPath)
                                    strAtmtNameTemp = strAtmtName(0) & _
                                                      Format(Now, "_mmddhhmmss") & _
                                                      Format(Timer * 1000 Mod 1000, "000")
                                    strAtmtPath = strFolderPath & strAtmtNameTemp & "." & strAtmtName(1)

                                    ' /* If the length of the saving path is over 260 characters.*/
                                    If Len(strAtmtPath) > MAX_PATH Then
                                        lCountEachItem = lCountEachItem - 1
                                        ' False: This attachment cannot be saved.
                                        blnIsSave = False
                                        Exit Do
                                    End If
                                Loop

                                ' /* Save the current attachment if it is a valid file name. */
                                If blnIsSave Then
 If itmOL.BodyFormat = olFormatHTML Then
                        'If the email is HTML type, the embeded picture need special care
                        Dim oPA As Outlook.PropertyAccessor
                        Dim PropName As String
                        Dim PropInfo As String

                        PropName = "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"
                        Set oPA = itmOL.Attachments.Item(i).PropertyAccessor
                        PropInfo = oPA.GetProperty(PropName)
                        If PropInfo = "Flase" Then atmt.SaveAsFile strAtmtPath
                            Else
                                lCountEachItem = lCountEachItem - 1
                            End If
                        Next
                    End If

                    ' Count the number of attachments in all Outlook items.
                    lCountAllItems = lCountAllItems + lCountEachItem
                Next
            End If
        Else
            MsgBox "Failed to get the handle of Outlook window!", vbCritical, "Error from Attachment Saver"
            blnIsEnd = True
            GoTo PROC_EXIT
        End If

    ' /* For run-time error:
    '    The Explorer has been closed and cannot be used for further operations.
    '    Review your code and restart Outlook. */
    Else
        MsgBox "Please select an Outlook item at least.", vbExclamation, "Message from Attachment Saver"
        blnIsEnd = True
    End If

PROC_EXIT:
    SaveAttachmentsFromSelection = lCountAllItems

    ' /* Release memory. */
    If Not (objFSO Is Nothing) Then Set objFSO = Nothing
    If Not (objItem Is Nothing) Then Set objItem = Nothing
    If Not (selItems Is Nothing) Then Set selItems = Nothing
    If Not (atmt Is Nothing) Then Set atmt = Nothing
    If Not (atmts Is Nothing) Then Set atmts = Nothing

    ' /* End all code execution if the value of blnIsEnd is True. */
    If blnIsEnd Then End
End Function

' #####################
' Convert general path.
' #####################
Public Function CGPath(ByVal Path As String) As String
    If Right(Path, 1) <> "\" Then Path = Path & "\"
    CGPath = Path
End Function

' ######################################
' Run this macro for saving attachments.
' ######################################
Public Sub ExecuteSaving()
    Dim lNum As Long

    lNum = SaveAttachmentsFromSelection

    If lNum > 0 Then
        MsgBox CStr(lNum) & " attachment(s) was(were) saved successfully.", vbInformation, "Message from Attachment Saver"
    Else
        MsgBox "No attachment(s) in the selected Outlook items.", vbInformation, "Message from Attachment Saver"
    End If
End Sub
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
habib
  • 1
  • I have edited your code so it is readable. I selected the entire block of code then clicked the curly brackets above the edit window. Easy when you know how. – Tony Dallimore Dec 13 '18 at 18:13
  • All emails have the same format: Header, Text body, Zero or more attachments. The Header contains subject, sender information, and lots of other stuff some of which you normally never see. Html bodies are so universal these days that the Text body might be empty. Everything else is an attachment including the Html body although Outlook does not treat it as a normal attachment. It is not easy to distinguish between an attachment that is an image within the Html body and an image that has been attached. – Tony Dallimore Dec 13 '18 at 18:15
  • Forget this code. What do you actually want to do? Do you want to select one or more mail items with attachments and then call a macro to save their attachments? There are other ways of selecting mail items. Is this method the most convenient for you? If I have to identify the mail item with the attachment I need then drag-and-drop is the easiest for me. Do you want to browse for the destination folder or is it fixed? Do you like the way the code renames files to avoid duplicate names. Write a complete description of what you want. Someone may then be able to help. – Tony Dallimore Dec 13 '18 at 18:18
  • You could try one of these to save attachments that are not images. https://stackoverflow.com/questions/37433626/exclude-signature-images-when-exporting-attachments-to-a-folder or https://stackoverflow.com/questions/23801737/count-outlook-vba-attachment. – niton Dec 14 '18 at 17:36
  • Hi Tony, where is the adjusted code ? I cant seem to find it – habib Dec 17 '18 at 21:01
  • Niton, those script will count on knowing the attachments formats, and that's not the case here. – habib Dec 17 '18 at 21:02
  • @habib I have not posted any adjusted code because I do not know what you want. The code you found does not do what you want but you do not say what you want. I ask about features of that code which I would not like. For example, the code requires the user to identify the emails from which attachments are to be extracted. Is this really what you want? Are these emails from a particular sender? Do these emails have a particular subject? If so, it would be possible for Outlook to identify these emails. – Tony Dallimore Dec 18 '18 at 17:20
  • You say you want to ignore attachments that are embedded in the body. I did investigate this requirement years ago and failed to find a totally reliable solution because the names of images within the Html did not match the names of the attachments. Could you, for example, ignore all attached images? – Tony Dallimore Dec 18 '18 at 17:25
  • no I couldn't, that was my request (downloading attachment and not email body pictures and documents). but I saw programs out there which dose exactly that am sure there code is based on something like that, that's why I asked since I didn't wanna pay for it without checking if I can build one my self. – habib Dec 18 '18 at 21:21
  • and just so we're clear I want to download the attachments only, and nothing from the email body content. I don't have limit on the emailers or file types either (note that the code I posted download everything in the email and has no limitation on the sender. – habib Dec 18 '18 at 21:29

2 Answers2

0

It must be ten years since I investigated embedded images. I do not remember the details now but it involved trying to distinguish between images that were attached and images that were embedded. At the time I was received many emails that contained both. Today, I cannot find a single email in my Inbox with embedded images that are attachments; the embedded images, signature and so on are all links to external sites.

The macro below is one of two I use to investigate emails I want to process. When I only need limited diagnostics, I use the version with Debug.Print. The macro below outputs to a desktop file named “InvestigateEmails.txt”. It outputs both the text and Html bodies but with carriage returns, linefeeds and tabs replaced by “{cr}”, “{lf}” and “{tb}”. This allows me to fully investigate the emails are they are and not as they display.

To use this macro, select one or more of these emails and run macro InvestigateEmails1. You need to study the output and identify the difference between the attachments you want to save and those you do not. Once you know how to identify the difference you will be able to ask a specific question.

Macro InvestigateEmails1 needs a reference to "Microsoft Scripting Runtime". Macro PutTextFileUtf8NoBom needs a reference to "Microsoft ActiveX Data Objects n.n Library". On my system “n.n” is “6.1” but the macro should run with earlier versions.

Public Sub InvestigateEmails1()

  ' Outputs properties of selected emails to a file.

  ' ???????  No record of when originally coded
  ' 22Oct16  Output to desktop file rather than Immediate Window.

  ' Technique for locating desktop from answer by Kyle:
  ' http://stackoverflow.com/a/17551579/973283
  ' Needs reference to "Microsoft Scripting Runtime"

  Dim Exp As Explorer
  Dim FileBody As String
  Dim fso As FileSystemObject
  Dim InxA As Long
  Dim ItemCrnt As MailItem
  Dim Path As String

  Path = CreateObject("WScript.Shell").SpecialFolders("Desktop")

  Set Exp = Outlook.Application.ActiveExplorer

  If Exp.Selection.Count = 0 Then
    Call MsgBox("Please select one or more emails then try again", vbOKOnly)
    Exit Sub
  Else
    FileBody = ""
    For Each ItemCrnt In Exp.Selection
      With ItemCrnt
        FileBody = FileBody & "From (Sender): " & .Sender & vbLf
        FileBody = FileBody & "From (Sender name): " & .SenderName & vbLf
        FileBody = FileBody & "From (Sender email address): " & _
                              .SenderEmailAddress & vbLf
        FileBody = FileBody & "Subject: " & CStr(.Subject) & vbLf
        If .Attachments.Count = 0 Then
          FileBody = FileBody & "No attachments" & vbLf
        Else
          FileBody = FileBody & "Attachments:" & vbLf
          FileBody = FileBody & "No.|Type|Path|Filename|DisplayName|" & vbLf
          For InxA = 1 To .Attachments.Count
            With .Attachments(InxA)
              FileBody = FileBody & InxA & "|"
              Select Case .Type
                Case olByValue
                  FileBody = FileBody & "Val"
                Case olEmbeddeditem
                  FileBody = FileBody & "Ebd"
                Case olByReference
                  FileBody = FileBody & "Ref"
                Case olOLE
                  FileBody = FileBody & "OLE"
                Case Else
                  FileBody = FileBody & "Unk"
              End Select
              ' Not all types have all properties.  This code handles
              ' those missing properties of which I am aware.  However,
              ' I have never found an attachment of type Reference or OLE.
              ' Additional code may be required for them.
              Select Case .Type
                Case olEmbeddeditem
                  FileBody = FileBody & "|"
                Case Else
                  FileBody = FileBody & "|" & .Pathname
              End Select
              FileBody = FileBody & "|" & .Filename
              FileBody = FileBody & "|" & .DisplayName & "|" & vbLf
            End With
          Next
        End If
        Call OutLongText(FileBody, "Text: ", Replace(Replace(Replace(.Body, vbLf, _
                         "{lf}" & vbLf), vbCr, "{cr}"), vbTab, "{tb}"))
        Call OutLongText(FileBody, "Html: ", Replace(Replace(Replace(.HtmlBody, vbLf, _
                         "{lf}" & vbLf), vbCr, "{cr}"), vbTab, "{tb}"))
        FileBody = FileBody & "--------------------------" & vbLf
      End With
    Next
  End If

  Call PutTextFileUtf8NoBom(Path & "\InvestigateEmails.txt", FileBody)

End Sub
Public Sub OutLongText(ByRef TextOut As String, ByVal Head As String, _
                       ByVal TextIn As String)

  ' Break TextIn into lines of not more than 100 characters
  ' and append to TextOut

  Dim PosEnd As Long
  Dim LenOut As Long
  Dim PosStart As Long

  If TextIn <> "" Then
    PosStart = 1
    Do While PosStart <= Len(TextIn)
      PosEnd = InStr(PosStart, TextIn, vbLf)
      If PosEnd = 0 Or PosEnd > PosStart + 100 Then
        ' No LF in remainder of TextIn or next 100 characters
        PosEnd = PosStart + 99
        LenOut = 100
      Else
        ' Output upto LF.  Restart output after LF
        LenOut = PosEnd - PosStart
        PosEnd = PosEnd
      End If
      If PosStart = 1 Then
        TextOut = TextOut & Head
      Else
        TextOut = TextOut & Space(Len(Head))
      End If
      TextOut = TextOut & Mid$(TextIn, PosStart, LenOut) & vbLf
      PosStart = PosEnd + 1
    Loop
  End If

End Sub
Public Sub PutTextFileUtf8NoBom(ByVal PathFileName As String, ByVal FileBody As String)

  ' Outputs FileBody as a text file named PathFileName using
  ' UTF-8 encoding without leading BOM

  ' Needs reference to "Microsoft ActiveX Data Objects n.n Library"
  ' Addition to original code says version 2.5. Tested with version 6.1.

  '  1Nov16  Copied from http://stackoverflow.com/a/4461250/973283
  '          but replaced literals with parameters.
  ' 15Aug17  Discovered routine was adding an LF to the end of the file.
  '          Added code to discard that LF.
  ' 11Oct17  Posted to StackOverflow
  '  9Aug18  Comment from rellampec suggested removal of adWriteLine from
  '          WriteTest statement would avoid adding LF.
  ' 30Sep18  Amended routine to remove adWriteLine from WriteTest statement
  '          and code to remove LF from file. Successfully tested new version.

  ' References: http://stackoverflow.com/a/4461250/973283
  '             https://www.w3schools.com/asp/ado_ref_stream.asp

  Dim BinaryStream As Object
  Dim UTFStream As Object

  Set UTFStream = CreateObject("adodb.stream")

  UTFStream.Type = adTypeText
  UTFStream.Mode = adModeReadWrite
  UTFStream.Charset = "UTF-8"
  UTFStream.Open
  UTFStream.WriteText FileBody

  UTFStream.Position = 3 'skip BOM

  Set BinaryStream = CreateObject("adodb.stream")
  BinaryStream.Type = adTypeBinary
  BinaryStream.Mode = adModeReadWrite
  BinaryStream.Open

  UTFStream.CopyTo BinaryStream

  UTFStream.Flush
  UTFStream.Close
  Set UTFStream = Nothing

  BinaryStream.SaveToFile PathFileName, adSaveCreateOverWrite
  BinaryStream.Flush
  BinaryStream.Close
  Set BinaryStream = Nothing

End Sub
Tony Dallimore
  • 12,335
  • 7
  • 32
  • 61
0

A simple demo of the answer shown here. Hidden attachments should be images.

Distinguish visible and invisible attachments with Outlook VBA

Private Sub AttachmentsHidden()

    Const PR_ATTACHMENT_HIDDEN = "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"

    Dim olObj As Object
    Dim olPA As propertyAccessor
    Dim olAtt As Attachment

    ' Open an appropriate mailitem
    Set olObj = ActiveInspector.currentItem

    If olObj.Class = olmail Then

        Debug.Print "  Subject: " & olObj.Subject

        For Each olAtt In olObj.Attachments

            Set olPA = olAtt.propertyAccessor

            If olPA.GetProperty(PR_ATTACHMENT_HIDDEN) = False Then
                Debug.Print "   " & olAtt.fileName & vbCr & "    not hidden"
                Debug.Print "    Save this?"
            Else
                Debug.Print "   " & olAtt.fileName & vbCr & "    hidden"
                Debug.Print "    Skip this?"
            End If

        Next

    End If

End Sub
niton
  • 8,771
  • 21
  • 32
  • 52