Monday, 5 August 2013

VBA Modules: Outlook: OL_BPMProcess v6.22


Read this for full information on these modules

This module is getting into serious space-age coding territory here (well, for me anyway).  If you're a data analyst, and you are fed up with having to deal with a hundred emailed reports, if you can make full use of this, you'll shave at least a day off your workload.  If you can do it without anyone finding out, you can achieve the same amount of work with zero effort, and spend the rest of your time clearing your other workload and developing other automation solutions, so that eventually, everyone thinks you're incredibly busy, when actually, you're just sat back watching Outlook do everything for you and browsing Monster for new jobs.

To give you a rough idea of what I achieve with this, I use this module to process approximately 5GB of data downloads from a web server, update 32GB of local databases, and push out 4GB of Excel reports, and all before I get into the office on Monday morning.

Now, I could be very sneaky and change the schedule so it only runs during the day, and makes me look like some kind of high throughput automaton, but I'm not sneaky.  I prefer to make myself out to be some kind of code wizard, even though I work for a company that prefers to employ Indians, Mexicans and Filipinos in a data centre instead of talented analysts & coders in-country.  Now, we're not the only globocorp who does that, so I don't feel remotely ashamed by admitting that we're creating jobs in the third world, and coincidentally we outsource some of this to a "service" centre run by one of my previous blue chip globocorp employers, so I do feel that one day I might have to do this very thing for someone else.  Transferable skills are the only advantage we have left -- so learn as many as you can and move on to something else.

Anyway, mini-rant over, here's the code.


Note that I use Outlook Rules to launch the relevant script when keywords are found in the email subject.  There is another very elegant way of doing this using Event triggers, but as with many elegant solutions, I find they aren't always reliable once you start making things complicated.  This approach works like a sledgehammer.  Something gone wrong?  Kill Outlook and start again and it'll put the mangled wreckage of failure to one side, pick up the mallet, and start bashing again where it left off.

Downsides: it's annoying when it gets stuck, which for me is quite often, because I'm a messy coder.

Also note that the following uses modAppsFirefox to download a file from a URL.  As of v6, it uses modProcedures to store Excel/Access/Download procedures for running later.

'OL_BPMprocess
'v6.22 2013-12-02 17:31
'always export to \\GBMNCWSA050\BPMpublic\VBA Modules\Outlook\

'===========================================================================
' HELP CONTACT
'===========================================================================
' Code is provided without warranty and can be stolen and amended as required.
'   Tom Parish
'   TJP@tomparish.me.uk
'   http://baldywrittencod.blogspot.com
'   DGF Help Contact: see BPMHelpContact module
'=========================================================================

'*************************************************************
' NOTE: all prior versions MUST be upgraded to v4.02 or later
'*************************************************************

'*****************************************************
'*****  WARNING: SETTINGS BELOW MUST BE AMENDED  *****
'*****************************************************

'===========================================================================
' OL_BPMprocess
'===========================================================================
' OL_ProcessANYREPORT
'   Routines for copying XLS/CSV (and extracting ZIP) attachments, and for
'   downloading reports from URL.
'
' OL_ProcessDATABASE
'   Routines for processing database updates automatically by trigger email.
'   Triggered by specific subject, e.g. "BPMAUTORUN DATABASE MONTHEND"
'
' OL_ProcessZIPIT
'   Routines for zipping attachments sent back by return (size limit applies).
'   Triggered by subject, e.g. "BPMAUTORUN ZIPIT free text"
'
' OLV_xxxxxxx
'   Simple functions for Outlook

'===========================================================================
' Additional modules required:
'===========================================================================
'   modAppsFirefox v3  (v2 if DL_WaitForCompletion = False)
'   modAppsOffice v4
'   modProcedures  (if DL_WaitForCompletion = True)
'   modZip v6
'   xlSharePoint  (for SP_fn_val)

'===========================================================================
' Additional References required:
'===========================================================================
'   BPMGB Outlook rule: BPMAUTORUN ANYREPORT
'   BPMGB Outlook rule: BPMAUTORUN DATABASE
'   BPMGB Outlook rule: BPMAUTORUN ZIPIT

'===========================================================================
' External applications required:
'===========================================================================
'   MS Access
'   MS Excel

'=========================================================================
' VERSION HISTORY
'=========================================================================
'   v6.22   OL_DATABASE: won't run if specified DBs are locked (see CRP/CRA)
'           olV_SubmitLog: renamed variables to make more sense
'   v6.21   OL_DATABASE: changed CRPCRA log file name
'   v6.20   OL_ANYREPORT: hides Excel window by default, allows automatic report updates
'   v6.19   OL_DATABASE: added GCCSDBREPORTS trigger
'   v6.18   OL_DATABASE: added pause to GCCS, prevent issues
'   v6.17   swapped email address for OL_DefaultEmail in module
'           OL_DATABASE: minor code tweaks, fixed Logis FTP code
'   v6.16   OL_DATABASE: added GCCS propagate routine
'   v6.15   OL_DATABASE: added Logis FTP routine (405)
'   v6.14   OL_DATABASE: better error handling for unrecognised routines
'   v6.13   OL_ANYREPORT: bugfix for false error report in simple download, added bNothingElseToDo
'   v6.12   OL_ANYREPORT: bugfix for false error report when saving attachments, added bAttSaved
'   v6.11   olXLapp: prevents bugs when using modAppsOffice.XLapp
'   v6.10   OL_Simple_Archive: bugfix for subject/folder name
'   v6.09   OL_v_URL: extracts URL from HTML tags <A HREF="url">
'           OL_v_URL: workaround to prefix incomplete URL in emails with Forwin DLL URL
'           OL_v_URL: bugfix for when forwarding trigger email to BPM.GB@dhl.com
'           OL_v_var: bugfix for when forwarding trigger email to BPM.GB@dhl.com
'           OL_Simple_Archive: bugfix for single-word subfolders
'   v6.08   OL_ProcessANYREPORT: major process fix for storing Downloads
'   v6.07   OL_ProcessANYREPORT: bugfix for stored Downloads
'   v6.06   OL_ProcessDATABASE: CRP/CRA conf email to triggerer, CC group mailboxes
'   v6.05   OL_ProcessANYREPORT: error handler for stored Download failure
'   v6.04   OL_ProcessDATABASE: updated CRP/CRA Control List v2 filename
'           olV_SubmitLog: added triggerer/recipient email address to log, bugfix
'   v6.03   OL_ProcessANYREPORT: bugfix
'           OL_SendEmail updated to v1.06
'   v6.02   OL_ProcessANYREPORT: modProcedures stores URL
'   v6.01   OL_ProcessANYREPORT: modProcedures error handling
'   v6.00   OL_ProcessANYREPORT: added modProcedures functionality
'   v5.00   merged code from modSpecialFolders
'   v4.05   OL_ProcessANYREPORT: handles dbn including pth
'   v4.04   OL_Simple_Archive: code rearrangement; marks read, flags complete
'   v4.03   OL_Simple_Archive: minor bugfix for failed email move (happens during debugging for some reason)
'           OL_ProcessANYREPORT: added cBsl and cFsl constants
'   v4.02   OL_SaveAttachment: stopped deleting ofn where pth & ofn = logpthfn
'           OL_ProcessANYREPORT: validity check for pth, includes \ or /
'   v4.01   external download routine now in modAppsFirefox
'   v4.00a  annotations only
'   v4.00   OL_v_vvar: rebuild
'           OL_ProcessZIPIT: rebuild
'           OL_ProcessANYREPORT: annotations
'           OL_ProcessDATABASE: merged modules, renamed macro
'           merged modules: OL_varPublic, OL_Attachments, OL_DBmacros
'           retired: OL_ProcessForwinReport, olV_get_From, olV_chk_Auto, olV_do_Auto
'***********************************************************************
' PRIOR VERSIONS MUST BE UPGRADED
'***********************************************************************
'=========================================================================
' OL_Attachments VERSION HISTORY
'=========================================================================
'   v3.06   OL_v_URL: works for any file format hyperlink
'   v3.05   OL_ProcessANYREPORT: changes to syntax in trigger failure reply
'   v3.04   OL_ProcessANYREPORT: bugfix in download errors
'   v3.03   OL_Simple_Archive: tgt folder dictated by UPPER CASE words
'           added OL_UpperCase
'           added OL_LowerCase (for posterity only, not used)
'   v3.02   OL_Simple_Archive: handles multiple attachments
'           OL_SaveAttachment: handles multiple attachments
'           OL_ProcessANYREPORT: handles multiple attachments
'           OL_ProcessANYREPORT: downloads files from Forwin CSV hyperlink
'           OL_v_URL: checks for specified pipe variable first
'   v3.01   merged olS and olZ modules into olA
'   v3.00   OL_ProcessForwinReport retired
'   v2.12   OL_ProcessAnyReport (replaces OL_ProcessForwinReport)
'=========================================================================
' OL_DBmacros VERSION HISTORY
'=========================================================================
'   v1.07   olD_ProcessDatabaseUpdates: runs OL_Simple_Archive
'   v1.06   olD_ProcessDatabaseUpdates: writes result to log, sends email
'   v1.05   olD_ProcessDatabaseUpdates: opens ReadOnly
'   v1.04   olD_ProcessDatabaseUpdates: XL quits afterwards
'   v1.03   olD_ProcessDatabaseUpdates: added CRP/CRA trigger and log
'=========================================================================
' OL_varPublic VERSION HISTORY
'=========================================================================
'   v2.15   removed xlShellAndWait and Excel references
'   v2.14   olV_submitlog: sends confirmation/failure email
'   v2.13   annotations only (no material effect)
'   v2.12   olV_submitlog: added logTimeTaken, logRecipientEmail, logpthfn
'   v2.11   bugfix for xlShellAndWait
'           updated log file path to GBMNCWSA050 share
'   v2.10   moved to GBMNCWSA050

Option Explicit

'*****************************************************
'*****  WARNING: SETTINGS BELOW MUST BE AMENDED  *****
'*****************************************************

'max email attachment size in BYTES (10000000 = 10MB)
Private Const maxAttSize As Double = 10000000

'log file location should be changed for end user, write access is required
Public Const LogFolder As String _
    = "\\GBMNCWSA050\BPMpublic\LogFiles\"  'v2.11

'CSV will be created/updated in above LogFolder
Public Const LogFileName As String _
    = "OutlookSaveAttachmentsLog.csv"

Private Const DL_WaitForCompletion As Boolean = False
'v6.00 True will always wait for completion of downloads, False requires modProcedures

'default email address for Trigger Failure and if recipient is specified as "" (mainly for testing)
Private Const OL_DefaultEmail As String = "bpm.gb@dhl.com"  'v5.00

'Specify name of default Mailbox - case sensitive
Private Const OL_MYmbx As String = "BPM.GB@dhl.com" 'v5.00

'*****************************************************



Private olXLapp As Excel.Application  'v6.11

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliSeconds As Long)

'==========================================================================
'==========================================================================
'==========================================================================
'==========================================================================

'
'   Code from modSpecialFolders module:
'

'http://answers.microsoft.com/en-us/office/forum/office_2010-customize/how-2-refer-to-desktop/97eba910-54c9-409f-9454-6d7c8d54d009
Private Declare Function SHGetSpecialFolderLocation _
                         Lib "shell32" (ByVal hwnd As Long, _
                                        ByVal nFolder As Long, ppidl As Long) As Long

Private Declare Function SHGetPathFromIDList _
                         Lib "shell32" Alias "SHGetPathFromIDListA" _
                             (ByVal Pidl As Long, ByVal pszPath As String) As Long

Private Declare Sub CoTaskMemFree Lib "ole32" (ByVal pvoid As Long)

'Desktop
Private Const CSIDL_INTERNET = &H1 'Internet Explorer (icon on desktop)
Private Const CSIDL_PROGRAMS = &H2 'Start Menu\Programs
Private Const CSIDL_CONTROLS = &H3 'My Computer\Control Panel
Private Const CSIDL_PRINTERS = &H4 'My Computer\Printers
Private Const CSIDL_PERSONAL = &H5 'My Documents
Private Const CSIDL_FAVORITES = &H6 '<user name>\Favorites
Private Const CSIDL_STARTUP = &H7 'Start Menu\Programs\Startup
Private Const CSIDL_RECENT = &H8 '<user name>\Recent
Private Const CSIDL_SENDTO = &H9 '<user name>\SendTo
Private Const CSIDL_BITBUCKET = &HA '<desktop>\Recycle Bin
Private Const CSIDL_STARTMENU = &HB '<user name>\Start Menu
Private Const CSIDL_DESKTOPDIRECTORY = &H10 '<user name>\Desktop
Private Const CSIDL_DRIVES = &H11 'My Computer
Private Const CSIDL_NETWORK = &H12 'Network Neighborhood
Private Const CSIDL_NETHOOD = &H13 '<user name>\nethood
Private Const CSIDL_FONTS = &H14 'Windows\fonts
Private Const CSIDL_TEMPLATES = &H15
Private Const CSIDL_COMMON_STARTMENU = &H16 'All Users\Start Menu
Private Const CSIDL_COMMON_PROGRAMS = &H17 'All Users\Programs
Private Const CSIDL_COMMON_STARTUP = &H18 'All Users\Startup
Private Const CSIDL_COMMON_DESKTOPDIRECTORY = &H19 'All Users\Desktop
Private Const CSIDL_APPDATA = &H1A '<user name>\Application Data
Private Const CSIDL_PRINTHOOD = &H1B '<user name>\PrintHood
Private Const CSIDL_LOCAL_APPDATA = &H1C '<user name>\Local Settings\Application Data (non roaming)
Private Const CSIDL_ALTSTARTUP = &H1D 'non localized startup
Private Const CSIDL_COMMON_ALTSTARTUP = &H1E 'non localized common startup
Private Const CSIDL_COMMON_FAVORITES = &H1F
Private Const CSIDL_INTERNET_CACHE = &H20
Private Const CSIDL_COOKIES = &H21
Private Const CSIDL_HISTORY = &H22
Private Const CSIDL_COMMON_APPDATA = &H23 'All Users\Application Data
Private Const CSIDL_WINDOWS = &H24 'Windows Directory
Private Const CSIDL_SYSTEM = &H25 'System Directory
Private Const CSIDL_PROGRAM_FILES = &H26 'C:\Program Files
Private Const CSIDL_MYPICTURES = &H27 'C:\Program Files\My Pictures
Private Const CSIDL_PROFILE = &H28 'USERPROFILE
Private Const CSIDL_SYSTEMX86 = &H29 'x86 system directory on RISC
Private Const CSIDL_PROGRAM_FILESX86 = &H2A 'x86 C:\Program Files on RISC
Private Const CSIDL_PROGRAM_FILES_COMMON = &H2B 'C:\Program Files\Common
Private Const CSIDL_PROGRAM_FILES_COMMONX86 = &H2C 'x86 Program Files\Common on RISC
Private Const CSIDL_COMMON_TEMPLATES = &H2D 'All Users\Templates
Private Const CSIDL_COMMON_DOCUMENTS = &H2E 'All Users\Documents
Private Const CSIDL_COMMON_ADMINTOOLS = &H2F 'All Users\Start Menu\Programs\Administrative Tools
Private Const CSIDL_ADMINTOOLS = &H30 '<user name>\Start Menu\Programs\Administrative Tools
Private Const CSIDL_CONNECTIONS = &H31 'Network and Dial-up Connections
Private Const MAX_PATH = 260
Private Const NOERROR = 0

Private Function SpecFolder(ByVal lngFolder As Long) As String
Dim lngPidlFound As Long
Dim lngFolderFound As Long
Dim lngPidl As Long
Dim strPath As String

strPath = Space(MAX_PATH)
lngPidlFound = SHGetSpecialFolderLocation(0, lngFolder, lngPidl)
If lngPidlFound = NOERROR Then
    lngFolderFound = SHGetPathFromIDList(lngPidl, strPath)
    If lngFolderFound Then
        SpecFolder = Left$(strPath, _
                           InStr(1, strPath, vbNullChar) - 1)
    End If
End If
CoTaskMemFree lngPidl
End Function

'==========================================================================
'==========================================================================
'==========================================================================
'==========================================================================

Sub OL_ProcessANYREPORT(Item As Outlook.MailItem)
'v6.20 2013-11-26 15:09
'saves a copy of attachment (if one exists) and renames it (if ofn specified)
'!! ZIP extract & rename only possible if true extension is part of zip filename
'!! e.g. Report.csv.zip
'and/or downloads file from any URL (if url specified)
'and/or downloads file from any* hyperlink embedded in email
'!! *tested for Forwin CSV only - but can be adapted easily for other hyperlinks
'and/or run DB update (if dbn and dbm specified)
'and/or run XLS macros (if xls specified)
'and/or send confirmation email (if cfm specified)
'then move email into ANYREPORT folder

'SYNTAX to test:
'OL_ProcessANYREPORT OL_GetCurrentItem

' SUBJECT HEADER FOR EMAIL MUST BE ENTERED AS FOLLOWS:
'====================================================================
' BPMAUTORUN ANYREPORT [Report identification free text]
'====================================================================

' VARIABLES MUST BE ENTERED IN EMAIL BODY (PLAIN TEXT) AS FOLLOWS:
' BPMAUTORUN variables must be on separate lines, in "quote marks",
' and separated by pipes |
' NB: pattern match looks for e.g. "|pth=" at start and ""|" at end
' pth is mandatory, others are optional
'====================================================================
'|subj="BPMAUTORUN ANYREPORT [Report identification free text]"
        'NB: subj is not actually used, just helps with making copies of schedule template
'|pth="\\SERVER\Share Name\Folder\Subfolder\"
'       'NB: required
'|ofn="Filename.ext"
        'NB: if not specified, simply uses original filename
'|dbn="Database.mdb/.accdb"
'|dbm="reload ALL"
        'NB: this machine must have Access 2007+ installed for .accdb
'|xls="UPDATE MACRO.xls"
        'NB: can specify full path to override pth above, otherwise looks in pth for this file
'|cfm="recipient.email@domain.com"
        'NB: separate multiple recipients with semicolon and space "; "
'|url="http://domain.com/folder/file.ext"
'|      'NB: final pipe is required to prevent errors with last quotation mark
'
'CSV (hyperlink)
        'NB: may only extract URL from Forwin notification hyperlinks - to be tested
'====================================================================

Dim pth As String, ofn As String, dbn As String, dbm As String, xls As String, cfm As String, url As String
pth = OL_v_var(Item, "pth")  'REQUIRED - if none of the following are specified, will simply save attachment to pth
ofn = OL_v_var(Item, "ofn")  'optional (required if URL is used)
dbn = OL_v_var(Item, "dbn")  'optional (required if dbm is provided)
dbm = OL_v_var(Item, "dbm")  'optional (required if dbn is provided)
xls = OL_v_var(Item, "xls")  'optional (NB: if a UNC is specified here, note that if testing by forwarding, Outlook converts to HYPERLINK automatically)
cfm = OL_v_var(Item, "cfm")  'optional (specifies recipient for email confirmation when done)
url = OL_v_URL(Item)         'optional (required for download)

Dim cfmmsg As String, cfmsub As String, blnFailed As Boolean
Dim dl_PathFile As String  'v6.00
Dim bStoreProc As Boolean, bStoreURL As Boolean 'v6.02 'v6.03
Dim bAttSaved As Boolean  'v6.12
Dim bNothingElseToDo As Boolean  'v6.13

If cfm = "" Then cfm = Item.SenderEmailAddress

Const cBsl As String = "\"
Const cFsl As String = "/"

If pth <> vbNullString Then
    If InStr(pth, cBsl) > 0 Or InStr(pth, cFsl) > 0 Then
  
    'EITHER
    'optional: save attachment(s)
        If Item.Attachments.Count > 0 Then
            If Item.Attachments.Count > 1 And ofn <> "" Then
                'blnFailed = True  'not a total failure
                cfmmsg = "Error: ofn cannot be specified with multiple attachments." & vbLf & vbLf
                OL_SaveAttachment Item, pth
                bAttSaved = True  'v6.12
                cfmmsg = cfmmsg & "Attachments saved to: " & pth & vbLf & vbLf
            Else
                OL_SaveAttachment Item, pth, ofn
                bAttSaved = True  'v6.12
                cfmmsg = "Attachment saved to: " & pth & ofn & vbLf & vbLf
            End If
    'OR
    'optional: download from URL
        ElseIf url <> vbNullString Then
        'url and pth are BOTH required to download file
        '!! ofn is mandatory for URL downloads as the downloaded filename is always gibberish
            If ofn = "" Then
                blnFailed = True
                cfmmsg = "Error: ofn is required to download from URL to " & pth & vbLf & vbLf
            Else
            'download file
                dl_PathFile = modAppsFirefox.ff_GetDownload(url, "", pth & ofn, DL_WaitForCompletion)  'v6.00
                If dl_PathFile = "" Then
                    blnFailed = True
                    cfmmsg = "Error: file could not be downloaded from URL to:" & pth & ofn & vbLf & vbLf
                ElseIf dl_PathFile = pth & ofn Then
                    bNothingElseToDo = True
                    cfmmsg = url & vbLf & vbLf & "File downloaded to: " & pth & ofn & vbLf & vbLf
                ElseIf dl_PathFile = url Then  '6.02
                    bStoreURL = True
                    cfmmsg = "File not downloaded, URL will be stored and should be downloaded later" & vbLf & vbLf & dl_PathFile & vbLf & pth & ofn & vbLf & vbLf
                Else
                    bStoreProc = True
                    cfmmsg = "File still downloading, procedure will be stored and should be run later" & vbLf & vbLf & dl_PathFile & vbLf & pth & ofn & vbLf & vbLf
                End If
            End If
        End If
      
    'optional: DB macros are not always required
        If dbn <> vbNullString And dbm <> vbNullString And blnFailed = False Then
            If (dbn <> vbNullString And dbm = vbNullString) Or (dbn = vbNullString And dbm <> vbNullString) Then
                blnFailed = True
                cfmmsg = cfmmsg & "Error: Database could not be updated, dbn and dbm are both required" & vbLf & vbLf
            Else
                If InStr(dbn, cBsl) > 0 Or InStr(xls, cFsl) > 0 Then
                'path is included within dbn string
                    If bStoreURL = True Then
                    'download later
                        If modProcedures.mp_Procedure_Store(url, "", pth & ofn, mpApps.mpAccess, dbn, dbm) = False Then  'v6.07
                            blnFailed = True
                            cfmmsg = "Error: couldn't store Download procedure" & vbLf & vbLf & url & vbLf & vbLf & pth & ofn & vbLf & vbLf
                        End If
                    ElseIf bStoreProc = True Then
                    'run procedure later
                        If modProcedures.mp_Procedure_Store(url, dl_PathFile, pth & ofn, 2, dbn, dbm) = False Then
                            blnFailed = True
                            cfmmsg = "Error: couldn't store procedure, dl_Pathfile already in use" & vbLf & vbLf & dl_PathFile & vbLf & vbLf
                        End If
                    Else
                        run_Access vbNullString, dbn, dbm
                        If DBapp Is Nothing Then
                            cfmmsg = cfmmsg & "Database updated: " & dbn & vbLf & vbLf
                        Else
                            Set DBapp = Nothing
                            blnFailed = True
                            cfmmsg = cfmmsg & "Database not updated, something went wrong: " & dbn & vbLf & vbLf
                        End If
                    End If
                Else
                'path is same as saved report
                    If bStoreURL = True Then
                    'download later
                        If modProcedures.mp_Procedure_Store(url, "", pth & ofn, mpApps.mpAccess, dbn, dbm) = False Then  'v6.07
                            blnFailed = True
                            cfmmsg = "Error: couldn't store Download procedure" & vbLf & vbLf & url & vbLf & vbLf & pth & ofn & vbLf & vbLf
                        End If
                    ElseIf bStoreProc = True Then
                        If modProcedures.mp_Procedure_Store(url, dl_PathFile, pth & ofn, 2, pth & dbn, dbm) = False Then
                            blnFailed = True
                            cfmmsg = "Error: couldn't store procedure, dl_Pathfile already in use" & vbLf & vbLf & dl_PathFile & vbLf & vbLf
                        End If
                    Else
                        run_Access pth, dbn, dbm
                        If DBapp Is Nothing Then
                            cfmmsg = cfmmsg & "Database updated: " & dbn & vbLf & vbLf
                        Else
                            Set DBapp = Nothing
                            blnFailed = True
                            cfmmsg = cfmmsg & "Database not updated, something went wrong: " & dbn & vbLf & vbLf
                        End If
                    End If
                End If
            End If
        End If
  
    'optional: XL macros are not always required, but advisable, because you can
    'autorun Access macros, or long series of SQL queries, with Excel VBA
        If xls <> vbNullString And blnFailed = False Then
            If InStr(xls, cBsl) > 0 Or InStr(xls, cFsl) > 0 Then
            'path is included within xls string
                If bStoreURL = True Then
                'download later
                    If modProcedures.mp_Procedure_Store(url, "", pth & ofn, mpApps.mpExcel, xls, "") = False Then  'v6.07
                        blnFailed = True
                        cfmmsg = "Error: couldn't store Download procedure" & vbLf & vbLf & url & vbLf & vbLf & pth & ofn & vbLf & vbLf
                    End If
                ElseIf bStoreProc = True Then
                'run procedure later
                    If modProcedures.mp_Procedure_Store(url, dl_PathFile, pth & ofn, mpApps.mpExcel, xls) = False Then
                        blnFailed = True
                        cfmmsg = "Error: couldn't store procedure, dl_Pathfile already in use" & vbLf & vbLf & dl_PathFile & vbLf & vbLf
                    End If
                Else
                    run_Excel XLpth:=vbNullString, XLfn:=xls, bForceVisibility:=False  'v6.20 bForceVisibility fixes issue with automatic update of some reports, only updates when invisible
                    If olXLapp Is Nothing Then
                        cfmmsg = cfmmsg & "Excel macros run: " & pth & xls & vbLf & vbLf
                    Else
                        Set olXLapp = Nothing
                        blnFailed = True
                        cfmmsg = cfmmsg & "Excel macros not run, something went wrong: " & pth & xls & vbLf & vbLf
                    End If
                End If
            Else
            'path is same as saved report
                If bStoreURL = True Then
                'download later
                    If modProcedures.mp_Procedure_Store(url, "", pth & ofn, mpApps.mpExcel, xls, "") = False Then  'v6.07
                        blnFailed = True
                        cfmmsg = "Error: couldn't store Download procedure" & vbLf & vbLf & url & vbLf & vbLf & pth & ofn & vbLf & vbLf
                    End If
                ElseIf bStoreProc = True Then
                    If modProcedures.mp_Procedure_Store(url, dl_PathFile, pth & ofn, mpApps.mpExcel, pth & xls) = False Then
                        blnFailed = True
                        cfmmsg = "Error: couldn't store procedure, dl_Pathfile already in use" & vbLf & vbLf & dl_PathFile & vbLf & vbLf
                    End If
                Else
                    run_Excel XLpth:=pth, XLfn:=xls, bForceVisibility:=False  'v6.20 bForceVisibility fixes issue with automatic update of some reports, only updates when invisible
                    If olXLapp Is Nothing Then
                        cfmmsg = cfmmsg & "Excel macros run: " & pth & xls & vbLf & vbLf
                    Else
                        Set olXLapp = Nothing
                        blnFailed = True
                        cfmmsg = cfmmsg & "Excel macros not run, something went wrong: " & pth & xls & vbLf & vbLf
                    End If
                End If
            End If
        End If
  
    'optional: just move downloading file once completed if no XL macros or DB specified 'v6.08
        If bAttSaved = False And xls = vbNullString And dbn = vbNullString And blnFailed = False And bNothingElseToDo = False Then    'v6.13  'v6.12
        'eventual path can be different or same as current download location
            If bStoreURL = True Then
            'download later
                If modProcedures.mp_Procedure_Store(url, "", pth & ofn, mpApps.mpExcel, "", "") = False Then  'v6.07
                    blnFailed = True
                    cfmmsg = "Error: couldn't store Download procedure" & vbLf & vbLf & url & vbLf & vbLf & pth & ofn & vbLf & vbLf
                End If
            ElseIf bStoreProc = True Then
            'run procedure later (actually doesn't run any procedure, because xls is "", could equally put it on Access sheet)
                If modProcedures.mp_Procedure_Store(url, dl_PathFile, pth & ofn, mpApps.mpExcel, "") = False Then
                    blnFailed = True
                    cfmmsg = "Error: couldn't store procedure, dl_Pathfile already in use?" & vbLf & vbLf & dl_PathFile & vbLf & vbLf
                End If
            Else
                blnFailed = True
                cfmmsg = cfmmsg & "Download process not stored, something went wrong: " & vbLf & vbLf & pth & ofn & vbLf & vbLf
            End If
        End If
  
    'send failure notification if nothing was done
        If cfmmsg = "" Then
            blnFailed = True
            cfmmsg = "Error: nothing to do." & vbLf & vbLf
        End If
  
    Else
    'pth doesn't contain any slashes, can't be valid, send failure notification
        blnFailed = True
        cfmmsg = "Error: pth invalid, no slashes."
    End If
Else
'pth not specified, send failure notification
    blnFailed = True
    cfmmsg = "Error: pth not specified, pth is mandatory."
End If


'send confirmation email
'NB: if cfm (optional) not specified, will send confirmation to modEmail.OL_DefaultEmail
If blnFailed = True Then
    Const cNotUsed = " (not used)"
    Const cMandatory = " (Mandatory)"
    Const cOptional = " (Optional)"
    Const cForwinURLOnly = " (NB: currently only tested with Forwin hyperlinks)"
    cfmsub = "Trigger Failed: " & Replace(Item.Subject, "BPMAUTORUN ANYREPORT ", "")
        cfmmsg = cfmmsg & vbLf _
        & vbLf _
        & "Original Message Body:" & vbLf _
        & Item.Body & vbLf _
        & vbLf _
        & "Correct Syntax for Email Body:" & vbLf & vbLf _
        & "|subj=" & Chr(34) & "BPMAUTORUN ANYREPORT FOLDER1 FOLDER2 [Report identification free text]" & Chr(34) & cNotUsed & vbLf _
        & "|pth=" & Chr(34) & "\\SERVER\Share Name\Folder\Subfolder\" & Chr(34) & cMandatory & vbLf _
        & "|ofn=" & Chr(34) & "Filename.ext" & Chr(34) & cOptional & "*" & vbLf _
        & "|dbn=" & Chr(34) & "Database.mdb/.accdb" & Chr(34) & cOptional & vbLf _
        & "|dbm=" & Chr(34) & "DB macro name" & Chr(34) & cOptional & vbLf _
        & "|xls=" & Chr(34) & "UPDATE MACRO.xls/m" & Chr(34) & cOptional & vbLf _
        & "|cfm=" & Chr(34) & "recipient.email@domain.com" & Chr(34) & cOptional & vbLf _
        & "|url=" & Chr(34) & "http://domain.com/folder/file.ext" & Chr(34) & cOptional & vbLf _
        & "|                  (Mandatory, this 'pipe' marks the end of the body text)" & vbLf _
        & vbLf _
        & "NB:" & vbLf _
        & " - UPPER CASE subject controls where completed trigger is filed (i.e. ANYREPORT > UPPER > CASE > Lower case text folder name)" & vbLf _
        & " - pipes | indicate the start of a variable" & vbLf _
        & " - anything on the line after " & Chr(34) & "variabletext" & Chr(34) & " is ignored" & vbLf _
        & " - attachment(s) will be saved to pth automatically" & vbLf _
        & " - ofn cannot be used with multiple attachments" & vbLf _
        & " - Hyperlinks within body text can also be downloaded, e.g.:" & vbLf _
        & "       CSV [hyperlink]" & cForwinURLOnly & vbLf _
        & " * ofn is Mandatory for hyperlink downloads" & vbLf

ElseIf bStoreProc = True Then
    cfmsub = "Procedure Stored: " & Replace(Item.Subject, "BPMAUTORUN ANYREPORT ", "")
    cfmmsg = cfmmsg & vbLf _
        & vbLf _
        & "Original Message Body:" & vbLf _
        & Item.Body

Else
    cfmsub = "Trigger Processed: " & Replace(Item.Subject, "BPMAUTORUN ANYREPORT ", "")
    cfmmsg = cfmmsg & vbLf _
        & vbLf _
        & "Original Message Body:" & vbLf _
        & Item.Body
End If

OL_Simple_Archive Item

OL_SendEmail _
    Email_Recipient:=cfm, _
    Email_RecipientBCC:=OL_DefaultEmail, _
    Email_Subject:=cfmsub, _
    Email_BodyText:=cfmmsg

End Sub

Sub OL_ProcessZIPIT(Item As Outlook.MailItem)
'v6.17 2013-10-31 11:20
'save a copy of attachment [to specified path]
'then zip and email back to sender [OR specified recipient]
'with default subject and body text [OR as specified]

'NB: this text is repeated below and used as body text syntax

' SUBJECT HEADER FOR EMAIL MUST BE ENTERED AS FOLLOWS:
'====================================================================
' BPMAUTORUN ZIPIT [Report identification free text]
'====================================================================

' VARIABLES MUST BE ENTERED IN EMAIL BODY (PLAIN TEXT) AS FOLLOWS:
' BPMAUTORUN variables must be on separate lines, in "quote marks",
' and separated by pipes |
' NB: pattern match looks for e.g. "|pth=" at start and ""|" at end
' all are optional
'====================================================================
'|subj="BPMAUTORUN ZIPIT [Report identification free text]"
'|fwd="email.address@domain.com"
'|pth="\\ServerName\ShareName\Folder\"
'|subj="Subject Header for Email"
'|sbod="Body text for email including signature, use VB character codes"
'|att="Attachment Name.zip"
'====================================================================

Const subjDef As String = "Zipped file attached"
Const sattDef As String = "Attachment.zip"
Dim sbodDef As String
sbodDef = "Please find your zipped attachment." & vbLf _
    & vbLf _
    & "Kind regards," & vbLf _
    & vbLf _
    & "BPM Autoresponder" & vbLf _
    & OL_DefaultEmail & vbLf _
    & vbLf _
    & "SUBJECT HEADER FOR EMAIL MUST BE ENTERED AS FOLLOWS:" & vbLf _
    & "====================================================================" & vbLf _
    & " BPMAUTORUN ZIPIT [Report identification free text]" & vbLf _
    & "====================================================================" & vbLf _
    & vbLf _
    & " VARIABLES MUST BE ENTERED IN EMAIL BODY (PLAIN TEXT) AS FOLLOWS:" & vbLf _
    & " BPMAUTORUN variables must be on separate lines, in " & Chr(34) & "quote marks" & Chr(34) & "," & vbLf _
    & " and separated by pipes |" & vbLf _
    & " NB: pattern match looks for e.g. " & Chr(34) & "|pth=" & Chr(34) & " at start and " & Chr(34) & " at end" & vbLf _
    & " all are optional" & vbLf _
    & "====================================================================" & vbLf _
    & "|subj=" & Chr(34) & "BPMAUTORUN ZIPIT [Report identification free text]" & Chr(34) & " (not used)" & vbLf _
    & "|fwd=" & Chr(34) & "email.address@domain.com" & Chr(34) & vbLf _
    & "|pth=" & Chr(34) & "\\ServerName\ShareName\Folder\" & Chr(34) & "  NB: this UNC share must be accessible to BPM.GB@dhl.com" & vbLf _
    & "|subj=" & Chr(34) & "Subject Header for Email" & Chr(34) & vbLf _
    & "|sbod=" & Chr(34) & "Body text for email including signature, use VB character codes" & Chr(34) & vbLf _
    & "|att=" & Chr(34) & "Attachment Name.zip" & Chr(34) & vbLf _
    & "===================================================================="

Dim pthdefault, pth As String, fwd As String, subj As String, sbod As String
Dim bPathFail As Boolean
Dim oMail As Outlook.MailItem
Set oMail = Application.Session.GetItemFromID(Item.EntryID)
Dim src As String, tgt As String
pth = OL_v_var(oMail, "pth")  'optional
fwd = OL_v_var(oMail, "fwd")  'optional
subj = OL_v_var(oMail, "subj")  'optional
'use defaults if not specified
If fwd = vbNullString Then fwd = oMail.SenderEmailAddress
If subj = vbNullString Then subj = subjDef
If sbod = vbNullString Then sbod = "Dear " & oMail.SenderName & "," & vbLf & vbLf & sbodDef

'check for multiple attachments
If oMail.Attachments.Count > 1 Then
    OL_SendEmail fwd, , OL_DefaultEmail, subj, "ZIPIT Error: cannot process multiple attachments", False
    Exit Sub
End If

'set default path
pthdefault = SpecFolder(CSIDL_PERSONAL) & "\BPM Tools\"
On Error Resume Next
MkDir pthdefault
pthdefault = pthdefault & "ZIPIT\"
MkDir pthdefault
On Error GoTo 0

'pth is optional to save original attachment
If pth <> vbNullString Then
'check the specified path exists and is accessible
    If Dir(pth, vbDirectory) <> "." Then bPathFail = True
Else
    bPathFail = True
End If

If bPathFail = True Then
'pth not specified or doesn't exist, save to default folder
    pth = pthdefault
End If

'save attachment to pth, delete from original email
src = OL_SaveAttachment(oMail, pth)

'set tgt zip in default folder
tgt = pthdefault & sattDef

'zip it
If Zip7Sub(src, tgt, True) <> 0 Then
    OL_SendEmail fwd, , OL_DefaultEmail, subj, "ZIPIT Error: please contact bpm.gb@dhl.com for advice", False
    Exit Sub
End If

'check zip file is below allowable size limit
Dim zAttSize As Long  'MB
zAttSize = FileLen(tgt) / 1048576  '(1024 * 1024)
If zAttSize > (maxAttSize / 1048576) Then
    OL_SendEmail fwd, , OL_DefaultEmail, subj, "ZIPIT Error: maximum attachment size of " & (maxAttSize / 1048576) & "MB exceeded, zip was " & zAttSize & "MB", False
    Exit Sub
End If

'send the email with zipped attachment
OL_SendEmail fwd, , , subj, sbod, False, tgt

'file email in ZIPIT folder
OL_Simple_Archive oMail

'log success at this point?

End Sub

Sub OL_ProcessDATABASE(Item As Outlook.MailItem)
'v6.22 2013-12-02 17:31

'triggers in scope, e.g. "BPMAUTORUN DATABASE MONTHEND"
'also need to replicate code below for new database process
Dim sProcess As String, bDoArchive As Boolean
Const cMONTHEND As String = "MONTHEND"
Const cCRPCRA As String = "CRP/CRA"
Const cGCCSprop As String = "GCCSPROPAGATE"     'v6.16
Const cGCCSreports As String = "GCCSDBREPORTS"  'v6.19
'NB: this doesn't use BPMAUTORUN DATABASE prefix:
Const cLogisFTP As String = "File transfer completed successfully."

Dim oMail As Outlook.MailItem
Set oMail = Application.Session.GetItemFromID(Item.EntryID)

Dim subj As String, rcpt As String, sbody As String, updname As String _
    , olAppObj As Object, WB As Object, logpthfn As String _
    , macroname As String, queryname As String, pthfn As String _
    , t As Single, s As Long

Dim DBIsLocked As Boolean, DBdependents() As String, d As Byte  'v6.22

'Dim logDateTime As String, logPathFile As String, logResult As String
'logResult = False

t = Now()

With oMail
'flag red, cleared later when completed
'NB: different code for Outlook 2007+
If val(Application.Version) < 12 Then
'2003 and before
    .FlagStatus = olFlagMarked
    .FlagIcon = olRedFlagIcon
    .Save
Else
'2007 and later
    .FlagStatus = olFlagMarked
    .FlagIcon = olRedFlagIcon
    .Save
End If
    subj = .Subject
    rcpt = .SenderEmailAddress
    sbody = .Body
End With

'------------------------------------------------------------------------------------------------------------
' Month End database updates (doesn't work on GBMNCWSA050 yet?)
'------------------------------------------------------------------------------------------------------------
sProcess = cMONTHEND
    If InStr(subj, " " & sProcess) > 0 Then
        logpthfn = "C:\Documents and Settings\bpmgb\Documents\MONTHEND log.csv"
        macroname = ") RELOAD ALL"
    'this isn't set yet! need to migrate to shared drive first
        pthfn = "C:\Documents and Settings\All Users\"
        Set olAppObj = XLlaunch(True)
        'do anything else?
        Set WB = olAppObj.Workbooks.Open(pthfn)
        WB.Sheets(1).Range("SelectedQuery").Value = macroname
        WB.Macros.CBDoThisMacro_Click True
        WB.Close SaveChanges:=True
        olAppObj.Quit
        t = CSng(Now() - t)     'time in decimal
        s = t * 60 * 60 * 24    'converts decimal to seconds
        olV_SubmitLog Now(), sProcess, "Processed", s, OL_DefaultEmail, ""
        olAppObj.Quit
        Set olAppObj = Nothing
        bDoArchive = True
        GoTo CleanUp
    End If

'------------------------------------------------------------------------------------------------------------
' Customer Reporting Pack - Customer Revenue Analysis
'------------------------------------------------------------------------------------------------------------
sProcess = cCRPCRA
    If InStr(subj, " " & sProcess) > 0 Then
        d = 2  'v6.22
        ReDim DBdependents(d) As String
        DBdependents(1) = "\\GBMNCWSA050\Databases\Forwin\Shipment Level\Forwin Shipment Level.laccdb"
        DBdependents(2) = "\\GBMNCWSA050\Databases\ComboData\GBSTSWSA030\CoRA.laccdb"
        For d = 1 To d
            If Dir(DBdependents(d)) <> "" Then
                DBIsLocked = True
                Exit For
            Next d
        Next d
        logpthfn = "\\ishare.dhl.com\sites\DGFUK\CR\CRP\CRPCRA trigger log.csv"  'v6.21
        macroname = ""
        updname = "CRP / CRA database"
        If DBIsLocked Then  'v6.22
            olV_SubmitLog Now(), sProcess, "Failed - database(s) locked", 0, , logpthfn
            modEmail.SendEmail rcpt _
                , "DGFUK_CustomerReportingOS@DHL.com; DGFUK_CustomerReportingCT@DHL.com" _
                , OL_DefaultEmail _
                , "GBMNCWSA050 Automated Response: " & updname & " NOT updated" _
                , updname & " could not be updated, as per trigger sent by " & oMail.Sender & ", because a source database is locked (in use).  Please contact bpm.gb@dhl.com for more information before sending another trigger." & vbLf & vbLf _
                    & "Database locked:" & vbLf & DBdependents(d) 'v6.22 'v6.06
        Else
            pthfn = "\\ishare.dhl.com\sites\DGFUK\CR\CRP\CRP Control List v2.xlsm"  'v6.04
            Set olAppObj = XLlaunch(True)
            'do anything else?
            Set WB = olAppObj.Workbooks.Open(FileName:=pthfn, ReadOnly:=True)
            'the following is done automatically in WB
                'wb.Sheets(1).Range("SelectedQuery").Value = macroname
                'wb.Macros.CBDoThisMacro_Click True
                'wb.Close SaveChanges:=True
                'olAppObj.Quit
            t = CSng(Now() - t)     'time in decimal
            s = t * 60 * 60 * 24    'converts decimal to seconds
            olV_SubmitLog Now(), sProcess, "Processed", s, , logpthfn
            modEmail.SendEmail rcpt _
                , "DGFUK_CustomerReportingOS@DHL.com; DGFUK_CustomerReportingCT@DHL.com" _
                , OL_DefaultEmail _
                , "GBMNCWSA050 Automated Response: " & updname & " updated" _
                , updname & " updated and CSV updated on iShare, as per trigger sent by " & oMail.Sender  'v6.06
            olAppObj.Quit
            Set olAppObj = Nothing
        End If
        bDoArchive = True  'v6.22: NB: this is necessary otherwise infinite failure loop occurs (until DB is unlocked)
        GoTo CleanUp
    End If

'------------------------------------------------------------------------------------------------------------
' FTP file transfer - reports from Logis  - v6.15
'------------------------------------------------------------------------------------------------------------
sProcess = cLogisFTP
    If InStr(subj, " " & sProcess) > 0 And UCase(rcpt) = "NBC.COMPUTEROPS@DHL.COM" Then
        logpthfn = "\\GBMNCWSA050\Databases\Logis\LogisFTP log.csv"
        If InStr(UCase(sbody), "405 REPORT") > 0 Then
            updname = "Logis 405 database"
            pthfn = "\\GBMNCWSA050\Databases\Logis\405.accdb"
            macroname = "sweep_FTP_for_405()"  'runs VBA function
        End If
        Set olAppObj = run_Access("", pthfn, macroname)
        Set olAppObj = Nothing
        t = CSng(Now() - t)     'time in decimal
        s = t * 60 * 60 * 24    'converts decimal to seconds
        olV_SubmitLog Now(), sProcess, "Processed", s, , logpthfn
        modEmail.SendEmail OL_DefaultEmail _
            , _
            , _
            , "GBMNCWSA050 Automated Response: " & updname & " updated" _
            , updname & " updated, as per trigger sent by " & oMail.Sender
        bDoArchive = True
        GoTo CleanUp
    End If

'------------------------------------------------------------------------------------------------------------
' GCCS PROPAGATION  - v6.16
'------------------------------------------------------------------------------------------------------------
sProcess = cGCCSprop
    If InStr(subj, " " & sProcess) > 0 Then
        Sleep 20000  'this is necessary to ensure the upload to iShare is completed by triggerer before running process
        logpthfn = "\\GBMNCWSA050\Databases\GCCS\GCCS user propagation log.csv"
        updname = "GCCS user propagation"
        pthfn = "\\ishare.dhl.com\sites\DGFUK\Admin\GCCS\GCCS Users.xlsm"
        Set olAppObj = run_Excel(XLpth:="", XLfn:=pthfn, rxlOpenReadOnly:=True)
        Set olAppObj = Nothing
        t = CSng(Now() - t)     'time in decimal
        s = t * 60 * 60 * 24    'converts decimal to seconds
        olV_SubmitLog Now(), sProcess, "Processed", s, , logpthfn
        modEmail.SendEmail rcpt _
            , _
            , OL_DefaultEmail _
            , "GBMNCWSA050 Automated Response: " & updname & " completed" _
            , updname & " completed, as per trigger sent by " & oMail.Sender
        bDoArchive = True
        GoTo CleanUp
    End If

'------------------------------------------------------------------------------------------------------------
' GCCS DB AND REPORTS - v6.19
'------------------------------------------------------------------------------------------------------------
sProcess = cGCCSreports
    If InStr(subj, " " & sProcess) > 0 Then
        Sleep 20000  'this is necessary to ensure the upload to iShare is completed by triggerer before running process
        logpthfn = "\\GBMNCWSA050\Databases\GCCS\GCCS reports log.csv"
        updname = "GCCS reports update"
        pthfn = "\\GBMNCWSA050\Databases\GCCS\GCCS MASTER v4.xlsm"
        Set olAppObj = run_Excel(XLpth:="", XLfn:=pthfn, rxlOpenReadOnly:=True)
        Set olAppObj = Nothing
        t = CSng(Now() - t)     'time in decimal
        s = t * 60 * 60 * 24    'converts decimal to seconds
        olV_SubmitLog Now(), sProcess, "Processed", s, , logpthfn
        modEmail.SendEmail rcpt _
            , _
            , OL_DefaultEmail _
            , "GBMNCWSA050 Automated Response: " & updname & " completed" _
            , updname & " completed, as per trigger sent by " & oMail.Sender
        bDoArchive = True
        GoTo CleanUp
    End If


CleanUp:

If bDoArchive Then
'remove flag and Save
    With oMail
        .FlagStatus = False
        .FlagIcon = False
        .ReminderSet = False
        .UnRead = False
        .Save
    End With  'oMail
    OL_Simple_Archive Item
Else
'send failure notification
    modEmail.SendEmail Email_Recipient:=rcpt, Email_RecipientBCC:=OL_DefaultEmail, Email_Subject:=subj & " failed"
End If

End Sub

Private Function OL_SaveAttachment(Item As Outlook.MailItem _
    , pth As String, Optional ofn As String) As String
'v4.02 2013-08-06 14:49
'saves attachment(s) (requires pth to be set in parent macro)
'if AttCount > 1, renames if ofn specified (doesn't work for ZIP attachments)
'deletes attachment(s) from email ONLY if successful
'ZIP attachments MUST be named "Original Filename.csv.zip" and only contain that one file

Dim OLapp As Outlook.Application
Set OLapp = CreateObject("Outlook.Application")
'Set OLapp = Application

Dim myAttachments As Outlook.Attachments, AttName As String, AttCount As Byte, Att As Byte
Dim objItem As Outlook.MailItem

Dim logDateTime As String, logPathFile As String, logResult As String
logResult = False

Const zext As String = ".zip"

With Item
'flag red, cleared later when completed
'NB: different code for Outlook 2007+
If val(Application.Version) < 12 Then
'2003 and before
    .FlagStatus = olFlagMarked
    .FlagIcon = olRedFlagIcon
    .Save
Else
'2007 and later
    .FlagStatus = olFlagMarked
    .FlagIcon = olRedFlagIcon
    .Save
End If
End With

Set myAttachments = Item.Attachments
AttCount = myAttachments.Count
If AttCount = 0 Then GoTo ResultFalseNoAttachment
On Error GoTo ResultFalseNoAttachment
For Att = 1 To AttCount
    With myAttachments.Item(Att)
        On Error GoTo 0
        logDateTime = CStr(Now())
      
'save attachment as ZIP then unzip and process contents
        If Right(.DisplayName, 4) = zext Then
      
        'attachment name is "Trimmed Text - Report Name.csv.zip"
            logPathFile = pth & .DisplayName
    'On Error Resume Next
            If Dir(logPathFile) <> "" Then Kill logPathFile  'deletes old temporary zip file if still there
    'On Error GoTo 0
    On Error GoTo ResultFalseZipFail
            .SaveAsFile logPathFile
        'saved file is "Trimmed Text - Report Name.ext.zip"
        'extract original file from temporary ZIP
            Zip7Sub pth, logPathFile, False   '=0 is success
    On Error GoTo 0
            Kill logPathFile  'deletes temporary ZIP, no longer required
        'extracted file is now "Report Name.ext" so change logPathFile
            logPathFile = pth & Replace(logPathFile, ".zip", "")
        'logPathFile is now "Report Name.ext"
        'rename logPathFile if ofn specified
        'rename logPathFile if ofn specified
            If ofn <> "" Then
    On Error Resume Next  'only usually fails if file doesn't exist
                Kill pth & ofn
    On Error GoTo 0
    On Error GoTo ResultFalseRenameFail
                Name logPathFile As pth & ofn
    On Error GoTo 0
                OL_SaveAttachment = pth & ofn
            Else
                OL_SaveAttachment = logPathFile
            End If
      
      
'save attachment as original file (not zipped)
        Else
      
        'attachment name is "Trimmed Text - Report Name.ext"
    On Error GoTo ResultFalseSaveFail
            logPathFile = pth & OL_fn_trim(.DisplayName)
            .SaveAsFile logPathFile
        'rename logPathFile if ofn specified
            If ofn <> "" And pth & ofn <> logPathFile Then  'v4.02 was deleting if ofn same filename
            'can only do this for one file
                If AttCount > 1 Then GoTo ResultFalseMultipleAttachmentsRenameFail
    On Error Resume Next
                Kill pth & ofn
    On Error GoTo 0
    On Error GoTo ResultFalseRenameFail
                Name logPathFile As pth & ofn
    On Error GoTo 0
                OL_SaveAttachment = pth & ofn
            Else
                OL_SaveAttachment = logPathFile
            End If
        'saved file is "Report Name.csv"
    On Error GoTo 0
      
        End If
  
    'completed with no errors
        logResult = "Success"
    End With  'Attachment
Next Att

'only gets this far if no errors (including writing to log)
With Item
'remove flag and save
    .FlagStatus = False
    .FlagIcon = False
    .ReminderSet = False
    .UnRead = False
    .Save  'is this required? probably not - saves during move step
'remove attachment and move to relevant folder
    OL_Simple_Archive Item
End With

GoTo ResultSubmit

ResultFalseNoAttachment:
logResult = "Failure: report not attached to email"
GoTo ResultSubmit

ResultFalseZipFail:
logResult = "Failure: ZIP attachment " & Att & " could not be saved/unzipped"
GoTo ResultSubmit

ResultFalseMultipleAttachmentsRenameFail:
logResult = "Failure: multiple (" & AttCount & ") attachments, cannot be renamed to " & ofn
GoTo ResultSubmit

ResultFalseRenameFail:
logResult = "Failure: saved attachment " & Att & " could not be renamed to " & ofn
GoTo ResultSubmit

ResultFalseSaveFail:
logResult = "Failure: CSV attachment " & Att & " could not be saved"
GoTo ResultSubmit

ResultFalseAttDelFail:
logResult = "Failure: attachment " & Att & " could not be removed"
GoTo ResultSubmit


ResultSubmit:
On Error GoTo 0
'report success/failure to log file
olV_SubmitLog logDateTime, logPathFile, logResult


'myOlApp.Quit
'Set myOlApp = Nothing

End Function

Private Function OL_fn_trim(ByVal DisplayName As String) As String
'v2.04 2012-11-08 09:19
'removes any of specified strings from attachment filename
'(could use Replace function instead)

'specify TOTAL strings to remove
Const smax As Byte = 2
Dim sr(1 To smax) As String, s As Byte, is_pos As Byte, sr_len As Byte
'specify each string to remove
sr(1) = "Lookups - "
sr(2) = " en"

OL_fn_trim = DisplayName
For s = 1 To smax
    sr_len = Len(sr(s))
    is_pos = InStr(OL_fn_trim, sr(s))
    If is_pos = 0 Then
    'sr(s) not found in OL_fn_trim
        Exit For
    Else
    'remove sr(s) from OL_fn_trim
        OL_fn_trim = Left(OL_fn_trim, is_pos - 1) & Mid(OL_fn_trim, is_pos + sr_len, Len(OL_fn_trim))
    End If
Next s

End Function

Private Function OL_v_URL(ByVal Item As Outlook.MailItem) As String
'v6.09 2013-09-20 13:00
'extracts "|url="http://specificurl.com/filename.ext"
' --> if not found, tries to extract "HYPERLINK " from plain text email
' --> if not found, extracts <a href="url"> from HTML (not tested)

'first check for user-specified URL variable, quit if found
OL_v_URL = OL_v_var(Item, "url")
If OL_v_URL <> "" Then Exit Function

Dim a As Long, b As Long, c As Integer, sbody As String
Const ptnHLNK As String = "HYPERLINK """
Const ptnHEnd As String = """" 'was """CSV", file type suffix is not relevant, just look for quot mark after URL
Const ptnATag As String = "<a href="""
Const ptnAEnd As String = """>"
Const ptnHTTP As String = "http"  'need this to validate HTML hyperlink extraction.  NB: may be http:// or https:// in valid URL
Const ptnFwin As String = "https://forwin.dhl.com/cognos8/cgi-bin/cognosisapi.dll"  'prefix added to "?b=" shortened URL

'extract URL from Body Text, quit if found
sbody = Item.Body
a = InStr(sbody, ptnHLNK)  'finds FIRST hyperlink, Forwin always adds this to end
If a > 0 Then
    c = a
    Do Until c = 0
        c = InStr(a + 1, sbody, ptnHLNK) 'finds NEXT hyperlink, Forwin always adds to END
        If c <> a And c <> 0 Then a = c
    Loop
    a = a + Len(ptnHLNK)
    b = InStr(a, sbody, ptnHEnd)
    c = b - a
    OL_v_URL = Mid(sbody, a, c)
End If
If OL_v_URL <> "" And Left(OL_v_URL, Len(ptnHTTP)) = ptnHTTP Then Exit Function  'second test helps when re-sending email to yourself, Outlook will convert UNC into hyperlinks

'extract URL from HTML links, quit if found - v6.09
sbody = Item.HTMLBody
a = InStr(sbody, ptnATag)
If a > 0 Then
'HTML A tag found
    c = a
    Do Until c = 0  'find LAST hyperlink, Forwin always adds to end
        c = InStr(a + 1, sbody, ptnATag) 'finds NEXT hyperlink
        If c <> a And c <> 0 Then a = c
    Loop
    a = a + Len(ptnATag)
    b = InStr(a, sbody, ptnAEnd)
    c = b - a
    OL_v_URL = Mid(sbody, a, c)
    'URL from Forwin sometimes excludes Forwin site DLL location for report downloads, so add that manually
    If Left(OL_v_URL, Len(ptnHTTP)) <> ptnHTTP Then
        OL_v_URL = ptnFwin & OL_v_URL
    End If
    'cleanup
    OL_v_URL = Replace(OL_v_URL, "&amp;", "&")
End If

End Function

Private Function OL_v_var(ByVal Item As Outlook.MailItem _
    , ByVal vvar As String) As String
'v6.09 2013-09-20 12:17
'retrieves specified variable from email body text
'vvar is "pth", will be amended to "|pth="
'variable must be surrounded by quote marks, i.e. |pth="http://somepath"
'returns no string if vvar not found

Dim iStartQuot As Integer, iEndQuot As Integer, iNextPipe As Integer
Dim btxt As String  'bodytext
Dim oMail As Outlook.MailItem
Set oMail = Application.Session.GetItemFromID(Item.EntryID)
btxt = oMail.Body
Const vpipe As String = "|"  'Chr(124)
Const vequa As String = "="  'Chr(61)
Const vquot As String = """" 'Chr(34)
Const ptnHLNK As String = "HYPERLINK """
Const ptnFILE As String = "file:///"
vvar = vpipe & vvar & vequa & vquot  'now searching for "|pth=" not "pth"

iStartQuot = InStr(btxt, vvar)  'position of vvar
If iStartQuot = 0 Then
'error, variable with pipe "|pth=" not found, exit
    OL_v_var = vbNullString
    Exit Function
End If

iStartQuot = iStartQuot + Len(vvar)  'position AFTER opening vquot (marks start of variable)
If Mid(btxt, iStartQuot, Len(ptnHLNK)) = ptnHLNK Then 'v6.09 workaround for forwarding triggers to retrigger, b/c Outlook converts UNC pth to hyperlink
    iStartQuot = iStartQuot + Len(ptnHLNK)
End If
iEndQuot = InStr(iStartQuot + 1, btxt, vquot)   'vEnd = position of NEXT vquot AFTER vvar (marks end of variable)
iNextPipe = InStr(iStartQuot + 1, btxt, vpipe)  'position of next pipe (for validity check)

If iEndQuot = 0 Or (iNextPipe > 0 And iEndQuot > iNextPipe) Then
'error, variable not surrounded by "", exit
    OL_v_var = vbNullString
    Exit Function
End If

OL_v_var = Replace(Mid(btxt, iStartQuot, iEndQuot - iStartQuot), ptnFILE, "")  'v6.09 workaround for forwarding triggers to retrigger, b/c Outlook converts UNC pth to hyperlink

End Function

Function OL_GetCurrentItem() As Object
'source: http://www.outlookcode.com/article.aspx?id=49
  
    Dim objApp As Outlook.Application
    Set objApp = Application
  
    On Error Resume Next
    Select Case TypeName(objApp.ActiveWindow)
        Case "Explorer"
            Set OL_GetCurrentItem = objApp.ActiveExplorer.Selection.Item(1)
        Case "Inspector"
            Set OL_GetCurrentItem = objApp.ActiveInspector.CurrentItem
        Case Else
            ' anything else will result in an error, which is
            ' why we have the error handler above
    End Select
  
    Set objApp = Nothing

End Function

Sub OL_Simple_Archive(Item As Outlook.MailItem)
'v6.10 2013-09-23 10:36
'puts email in folder according to email subject, deletes attachment(s), marks read & flags complete
'NB: Outlook rule should be set to prevent email replies on this subject from being processed this way

Dim OLapp As Outlook.Application
'Set OlApp = CreateObject("Outlook.Application")
Set OLapp = Application

Dim objNS As Outlook.NameSpace, objFolder As Outlook.MAPIFolder, ParentFolder As Outlook.MAPIFolder
Set objNS = OLapp.GetNamespace("MAPI")
Set objFolder = objNS.Folders(OL_MYmbx).Folders("Inbox")

Dim blnFolderCreated As Boolean

'identify subfolder from CAPITALISED SUBJECT PREFIX
'e.g. "BPMAUTORUN ANYREPORT FORWIN MAPPING Free Text" goes in BPMAUTORUN > ANYREPORT > FORWIN > MAPPING > Free Text
Dim subj As String, s As Integer, olFolderName As String, CapturePrefix As String

Dim oMail As Outlook.MailItem
Set oMail = Application.Session.GetItemFromID(Item.EntryID)
subj = SP_fn_val(oMail.Subject, "")  'v6.10 fixes issue with slash etc. in folder name

'capture "Report: " (generic scheduled reports)
    CapturePrefix = "Report: "
    If InStr(subj, CapturePrefix) Then
    'create/use FOLDERNAME
        olFolderName = "REPORTS"
        Set ParentFolder = objFolder
        blnFolderCreated = OL_Create_objFolder(olFolderName, ParentFolder)
        If blnFolderCreated = True Then Set ParentFolder = Nothing
        Set objFolder = ParentFolder.Folders(olFolderName)
        subj = Mid(subj, Len(CapturePrefix) + 1, Len(subj))
    End If

s = InStr(subj, " ")
Do While s > 0
    s = InStr(subj, " ")  'v6.09 bugfix for 1-word subfolders e.g. "ANYREPORT FORWIN TSP Routing"
    If s = 0 Then s = Len(subj) + 1
    olFolderName = Left(subj, s - 1)
    If OL_UpperCase(olFolderName) Then
    'create/use FOLDERNAME
        Set ParentFolder = objFolder
        blnFolderCreated = OL_Create_objFolder(olFolderName, ParentFolder)
        If blnFolderCreated = True Then Set ParentFolder = Nothing
        Set objFolder = objFolder.Folders(olFolderName)
        If olFolderName = "DATABASE" Then Exit Do  'v6.10 bugfix for v6.09 doing DATABASE triggers
        subj = Mid(subj, s + 1, Len(subj))
    Else
    'finished extracting CAPSFOLDERNAMES, use remaining free text as destination folder
        olFolderName = subj
        Set ParentFolder = objFolder
        blnFolderCreated = OL_Create_objFolder(olFolderName, ParentFolder)
        If blnFolderCreated = True Then Set ParentFolder = Nothing
        Set objFolder = objFolder.Folders(olFolderName)
        s = 0
    End If
Loop

Set oMail = Nothing

'delete any attachments from mail (saves on mailbox storage)
Dim AttCount As Byte, Att As Byte
On Error Resume Next
If Item.Attachments.Count > 0 Then
    For Att = 1 To Item.Attachments.Count
        Item.Attachments.Item(Att).Delete
    Next Att
End If
On Error GoTo 0


'mark unread, flag complete, move to specified subfolder
With Item
On Error Resume Next
    .UnRead = False
    .FlagStatus = olFlagComplete
    .Save
    .Move objFolder  'on error, check whether blnFolderCreated is True/False, usually False means it already exists!
On Error GoTo 0
End With

End Sub

Private Function OL_Create_objFolder(FolderName As String _
    , Optional ParentFolder As Outlook.MAPIFolder) As Boolean
'v2.11 2013-01-10 13:15

Dim olOutlook As Outlook.Application

On Error GoTo ErrorHandler
Set olOutlook = Application
'if using outside Outlook e.g. within Excel:
'Set olOutlook = CreateObject("Outlook.Application")
If ParentFolder Is Nothing Then
    Dim ns As Outlook.NameSpace
    Set ns = olOutlook.GetNamespace("MAPI")
    Set ParentFolder = ns.GetDefaultFolder(olFolderInbox)
End If
ParentFolder.Folders.Add FolderName
On Error GoTo 0

Set olOutlook = Nothing
Set ns = Nothing
Set ParentFolder = Nothing

OL_Create_objFolder = True
Exit Function

ErrorHandler:
On Error GoTo 0
OL_Create_objFolder = False

End Function

Private Function OL_UpperCase(stringToCheck As String) As Boolean
'v3.03 2013-07-19 18:21
'source: http://www.freevbcode.com/ShowCode.asp?ID=5198
    OL_UpperCase = StrComp(stringToCheck, UCase(stringToCheck), vbBinaryCompare) = 0
End Function

Private Function OL_LowerCase(stringToCheck As String) As Boolean
'v3.03 2013-07-19 18:21
'source: http://www.freevbcode.com/ShowCode.asp?ID=5198
    OL_LowerCase = StrComp(stringToCheck, LCase(stringToCheck), vbBinaryCompare) = 0
End Function

Function olV_SubmitLog(ByVal logDateTime As String, ByVal logPathFileProcessed As String _
    , ByVal logResult As String _
    , Optional ByVal logTimeTaken As Integer, Optional ByVal logRecipientEmail As String _
    , Optional ByVal logToPathFilename As String)
'v6.22 2013-12-02 17:14
'also sends email to administrator/triggerer on success/failure

If logToPathFilename = "" Then logToPathFilename = LogFolder & LogFileName  'default log file location, unless specified
'commented, v6.04 creates log
'If Dir(logToPathFilename) = vbNullString Then MsgBox "CSV not found at " & logToPathFilename

On Error Resume Next

Dim logmsg(1 To 5) As String
logmsg(1) = Chr(34) & logDateTime & Chr(34) & ","   'timestamp
logmsg(2) = Chr(34) & logPathFileProcessed & Chr(34) & ","   'file that succeeded/failed, required, but can be "" if not relevant
logmsg(3) = Chr(34) & logResult & Chr(34) & ","     'result
If logTimeTaken > 0 Then logmsg(4) = logTimeTaken & "," Else logmsg(4) = ","      'time taken in seconds
If logRecipientEmail <> "" Then logmsg(5) = Chr(34) & logRecipientEmail & Chr(34)      'trigger sender (if specified)

'append log file (CSV)
Dim echostring As String
If Dir(logToPathFilename) = "" Then
    echostring = "cmd /c echo " _
        & """Date"",""Routine"",""Result"",""Duration"",""Triggered By Email""" _
        & " >> " _
        & Chr(34) & logToPathFilename & Chr(34)
    Shell echostring, vbHide
    Dim p As Byte
    Do While Dir(logToPathFilename) = "" And p < 100
        Sleep 100   'v6.04 waits to create logToPathFilename
        p = p + 1   'v6.04 stops infinite loop
    Loop
End If

logToPathFilename = Chr(34) & logToPathFilename & Chr(34)  'adds speech marks for CSV and result notification

If p < 100 Then
    echostring = "cmd /c echo " _
        & logmsg(1) _
        & logmsg(2) _
        & logmsg(3) _
        & logmsg(4) _
        & logmsg(5) _
        & " >> " _
        & logToPathFilename
    Shell echostring, vbHide
Else  'v6.04, couldn't add to log
    logToPathFilename = logToPathFilename & vbLf & "NB: couldn't create log, check this path is correct"
End If

'send result email
If logRecipientEmail <> "" Then
    OL_SendEmail logRecipientEmail, "", OL_MYmbx, "GBMNCWSA050 Automated Response: " & logPathFileProcessed & " " & logResult, "The triggered update request is now completed.  Result: " & logResult & vbLf & vbLf & logToPathFilename, False
End If

'On Error GoTo 0  'only required if causing problems

End Function

Sub OL_test()

With OL_GetCurrentItem
'flag as Completed and Save
    .FlagStatus = False
    .FlagIcon = False
    .ReminderSet = False
    .UnRead = False
    .Save
End With

End Sub

Function OL_SendEmail(ByVal Email_Recipient As String, Optional ByVal Email_RecipientCC As String _
    , Optional ByVal Email_RecipientBCC As String, Optional ByVal Email_Subject As String _
    , Optional ByVal Email_BodyText As String, Optional ByVal DisplayMsg As Boolean = False _
    , Optional AttachmentPath) As Byte
'code matched to modEmail.SendEmail
'v1.06 2013-09-02 10:56
'results: 0=success, 1=fail
'original source: http://support.microsoft.com/kb/161088
'v1.05 bugfix: http://stackoverflow.com/questions/13019651/automated-email-generation-not-resolving-multiple-recipients

On Error GoTo SendEmailError

Dim objOutlook As Object        'Outlook.Application
'Dim objOutlookMsg 'As Object     'Outlook.MailItem
Dim objOutlookRecip As Object   'Outlook.Recipient
Dim objOutlookAttach As Object  'Outlook.Attachment
'http://stackoverflow.com/questions/13019651/automated-email-generation-not-resolving-multiple-recipients
Dim EmailList As Variant, NumEmails As Long, AddEmailLoop As Long

' Create the Outlook session.
If InStr(Application.Name, "Outlook") = 0 Then
    Set objOutlook = CreateObject("Outlook.Application")
Else
    Set objOutlook = Application
End If

' Create the message.
'Set objOutlookMsg = objOutlook.CreateItem(0) 'olMailItem
Set objOutlook = objOutlook.CreateItem(0) 'olMailItem

'With objOutlookMsg
With objOutlook
    ' Add the To recipient(s) to the message.
    If Email_Recipient = "" Then
        Set objOutlookRecip = .Recipients.Add(cDefaultEmail)  'for testing/blunt force only
    Else
    'http://stackoverflow.com/questions/13019651/automated-email-generation-not-resolving-multiple-recipients
        Email_Recipient = Replace(Email_Recipient, ";", "; ")  'v1.06
        EmailList = Split(Email_Recipient, ";")
        NumEmails = UBound(EmailList)
        For AddEmailLoop = 0 To NumEmails
            Set objOutlookRecip = .Recipients.Add(EmailList(AddEmailLoop))
            objOutlookRecip.Type = 1 'olTo
            objOutlookRecip.Resolve
        Next
    End If

    ' Add the CC recipient(s) to the message.
    If Email_RecipientCC <> "" Then
        Email_RecipientCC = Replace(Email_RecipientCC, ";", "; ")  'v1.06
        EmailList = Split(Email_RecipientCC, "; ")
        NumEmails = UBound(EmailList)
        For AddEmailLoop = 0 To NumEmails
            Set objOutlookRecip = .Recipients.Add(EmailList(AddEmailLoop))
            objOutlookRecip.Type = 2 'olCC
            objOutlookRecip.Resolve
        Next
    End If
  
   ' Add the BCC recipient(s) to the message.
    If Email_RecipientBCC <> "" Then
        Email_RecipientBCC = Replace(Email_RecipientBCC, ";", "; ")  'v1.06
        EmailList = Split(Email_RecipientBCC, "; ")
        NumEmails = UBound(EmailList)
        For AddEmailLoop = 0 To NumEmails
            Set objOutlookRecip = .Recipients.Add(EmailList(AddEmailLoop))
            objOutlookRecip.Type = 3 'olBCC
            objOutlookRecip.Resolve
        Next
    End If

   ' Set the Subject, Body, and Importance of the message.
   .Subject = Email_Subject
   .Body = Email_BodyText & vbCrLf & vbCrLf
   .Importance = 2  'olImportanceHigh  'High importance

   ' Add attachments to the message.
   If Not IsMissing(AttachmentPath) Then
       Set objOutlookAttach = .Attachments.Add(AttachmentPath)
   End If

   ' Resolve each Recipient's name.  'v1.06 now resolved separately on addition
'   For Each objOutlookRecip In .Recipients
'       objOutlookRecip.Resolve
'   Next

   ' Should we display the message before sending?
   If DisplayMsg Then
       .Display
   Else
       .Save
       .Send
   End If
End With

Set objOutlook = Nothing
OL_SendEmail = 0  'no error
Exit Function

SendEmailError:
OL_SendEmail = 1  'general failure
End Function

No comments:

Post a Comment