Monday, 5 August 2013

VBA Modules: modAppsOffice v4.08

Read this for full information on these modules

modAppsOffice is a module I use all the time.  It has a few simple but endlessly useful functions for launching Access and Excel, and running SQL/Access macros/queries.  I probably haven't developed this one as much as I could have, but my needs are fairly simple!  I prefer to handle more complicated requirements within Excel using the Workbook_Open event to launch other custom macros.

This makes use of modKeyState by Chip Pearson - included within the module as of v4.

'modAppsOffice
'v4.08 2013-11-06 13:26

'===========================================================================
' 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 v3.14 or later
'*************************************************************

'********************************************
'****  two settings to be changed below  ****
'********************************************

'===========================================================================
' modAppsOffice
'===========================================================================
'   Routines for launching MS Office applications, opening files, running
'   macros, etc.  Requires application reference libraries to be enabled
'   via Tools > References ONLY if you are running from a different Office
'   application (e.g. launching Excel from Access).  Enabling unnecessary
'   libraries will cause no harm.

'===========================================================================
' Additional modules required:
'===========================================================================
'   None

'===========================================================================
' Additional References required:
'===========================================================================
'   None

'===========================================================================
' External applications required:
'===========================================================================
'   Microsoft Outlook   (for Outlook functions)
'   Microsoft Access    (for Access functions)
'   Microsoft Excel     (for Excel functions)

'=========================================================================
' VERSION HISTORY
'=========================================================================
'   v4.08   run_Excel: bugfix in error handler
'   v4.07   run_Excel: added bForceVisibility (defaults to maoVisibleByDefault)
'           run_Excel: added bForceVisibility
'           XL_launch: changed XLvisible to bForceVisibility
'   v4.06   mao_fix_path bugfix when pth includes fn
'   v4.05   run_Access: permits use of VB "function()" as DBmacro
'                       strQuery variable relabelled as SQLcommand
'   v4.04   run_Access/run_Excel: persistence bugfix
'   v4.03   run_Excel: RunNative bugfix
'   v4.02   run_Access/run_Excel error handlers (Not DBapp/XLapp Is Nothing)
'   v4.01   retired runOutlook_SendMail, use modEmail instead
'   v4.00   added optional SharePoint domain to check when opening XL_WB
'   v3.18   maoVisibleByDefault set Public for use with other modules
'           XLlaunch: visibility bugfix
'   v3.17   run_Excel: can specify Read-Only (rxlOpenReadOnly)
'   v3.16   run_Excel: determine Read-Only/Editable from iShare/not
'   v3.15   late bound references
'   v3.14   changed Access.Application to Object, fixes Debug issue for
'           users without MS Access installed - OL and XL still required
'*************************************************************
'   v3.13   minor bugfix in run_Excel (just saves a little time)
'   v3.12   MAJOR BUGFIX in XLlaunch
'*************************************************************
'!! v3.11   fixed persistent DBapp and XLapp issue (Objects set Public)
'           moved to GBMNCWSA050, annotations changed
'   v3.10a  MAJOR BUGFIX mao_fix_path
'*************************************************************
'!! v3.10   FAULTY mao_fix_path adds the last slash to paths
'*************************************************************
'   v3.09   annotations improved, no functional change
'   v3.08   run_AppName application objects changed to AppName.Application
'           runOutlook_SendMail tested in 2010
'           ** DOESN'T WORK IN OFFICE 2003 **
'   v3.07   run_Access changed to function (so XLapp, DBapp not required)
'   v3.03   runOutlook_SendMail sSubject, sRecipient, sBodyText, [DisplayFirst], [sAttach_pthfn1], [sAttach_pthfn2]
'   v2.xx   run_Excel XLpth, XLfn, [XLmacro], [RunNative], [LeaveOpenWhenDone](v2.09), [OpenOnly](v3.03)
'   v1.xx   run_Access DBpath, DBfn, [DBmacro], [DBquery], [strQuery], [LeaveOpenWhenDone](v2.09), [OpenOnly](v3.03)

Option Explicit
'References must be enabled via Tools > References
'Public prevents app always closing when macro ends
'--> must Set XXapp = Nothing to clean up
Public DBapp As Object 'Access.Application  'requires Access object library
Public OLapp As Object 'Outlook.Application 'requires Outlook object library
Public XLapp As Object 'Excel.Application   'requires Excel object library

'********************************************
'****  two settings to be changed below  ****
'********************************************

Public Const maoVisibleByDefault As Boolean = True  'v3.18 set public for use with other modules
    'set this to False for background-only operations

Private Const SP_domain As String = "ishare.dhl.com"
    'SharePoint domain, optional, "" if not required

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

Function run_Access(ByVal DBpath As String, ByVal DBfn As String _
    , Optional ByVal DBmacro As String _
    , Optional ByVal DBquery As String _
    , Optional ByVal SQLcommand As String _
    , Optional ByVal LeaveOpenWhenDone As Boolean _
    , Optional ByVal OpenOnly As Boolean _
    , Optional ByVal bForceVisibility As Boolean = maoVisibleByDefault) _
    As Object 'v3.14 was Access.Application  'v3.08 requires Microsoft Access object library via Tools > References
'v4.07 2013-11-04 14:04
'runs Access and opens DB [then runs Query OR Macro OR SQL command]

'DBpath   = "C:\JBA\"
DBpath = mao_fix_path(DBpath)  'v3.10
'DBfn   = "JBA G7 Detail.mdb"
'DBmacro  = "macro name 1"
'DBquery  = "query name 1"
'SQLcommand = "SELECT stuff FROM table WHERE this"

Set DBapp = Nothing  'v4.04

Dim strcount As Byte, pp As String, tt As String
Dim InXL As Boolean 'v3.07, v3.11
If InStr(Application.Name, "Excel") > 0 Then InXL = True
If InXL = True Then
    Set XLapp = Application  'v3.07
    XLapp.DisplayAlerts = False  'v3.07
End If

strcount = 0
tt = "FATAL ERROR"
pp = "Warning: can only process DBmacro OR DBquery OR SQLcommand." & vbLf
If DBmacro <> "" Then
    strcount = strcount + 1
    pp = pp & " - specified DBmacro " & DBmacro & vbLf
End If
If DBquery <> "" Then
    strcount = strcount + 1
    pp = pp & " - specified DBquery " & DBquery & vbLf
End If
If SQLcommand <> "" Then
    strcount = strcount + 1
    pp = pp & " - specified SQLcommand " & SQLcommand & vbLf
End If
If (strcount = 0 And OpenOnly = False) _
Or strcount > 1 Then
    MsgBox pp, , tt
    Exit Function
End If

Set run_Access = CreateObject("Access.Application")

On Error GoTo ErrorHandler  'leaves DBapp <> Nothing on error

With run_Access
    .Visible = bForceVisibility
'errors here means DBpath or DBfn is wrong
    .OpenCurrentDatabase DBpath & DBfn
'errors here could mean Excel Connection is not Read-Only
'solution here:   http://social.msdn.microsoft.com/Forums/en/sqlintegrationservices/thread/d03e4b1a-6be0-4b3c-8b31-42d6fc79bf39
    If OpenOnly = False Then
        If DBmacro <> "" Then
        'OL macro fails here doing Append macro - works when rerun - add a 2 second delay?
        'Application.Wait Val(Now() + TimeSerial(0, 0, 5))
            If Right(DBmacro, 2) = "()" Then  'v4.05 runs VB function or DB macro as required
            'DBmacro ends with "()", run VB function
                DBmacro = Left(DBmacro, Len(DBmacro) - 2)  'remove "()" from function name
                run_Access.Run DBmacro
            Else
            'run DB macro
                .DoCmd.RunMacro DBmacro
            End If
        ElseIf DBquery <> "" Then
            .DoCmd.OpenQuery DBquery
        ElseIf SQLcommand <> "" Then
            .DoCmd.RunSQL SQLcommand
        End If
    End If
    If maoVisibleByDefault = False Then
        .Visible = OpenOnly
    End If
    If OpenOnly = False And LeaveOpenWhenDone = False Then
        .CloseCurrentDatabase
        .Quit
        Set run_Access = Nothing
    Else
        If DBapp Is Nothing Then Set DBapp = run_Access 'Else MsgBox "Cannot persist >1 instance of Access"  'v3.11
        Set run_Access = Nothing
        .Visible = True
        Set XLapp = Nothing
        Exit Function  'v4.04 stops Access quitting
    End If
End With

ErrorHandler:   'leaves DBapp <> Nothing

Set DBapp = run_Access

If InXL = True Then
    XLapp.DisplayAlerts = True  'v3.07
End If

Set XLapp = Nothing

End Function

Function run_Excel(ByVal XLpth As String, ByVal XLfn As String _
    , Optional ByVal XLmacro As String _
    , Optional ByVal RunNative As Boolean _
    , Optional ByVal LeaveOpenWhenDone As Boolean _
    , Optional ByVal rxlOpenReadOnly As Boolean = False _
    , Optional ByVal bForceVisibility As Boolean = maoVisibleByDefault) _
    As Object
'v4.08 2013-11-06 13:26
'simply runs Excel and opens WB  [then runs macro] --> will run XLmacro when opened, if optional macro name specified
'WB should normally have macros that run on Workbook.Open
'v3.16 uses xlSharePoint, such files are ALWAYS opened Read-Only to allow automation and bypass message boxes
'otherwise use IsShiftKeyDown=True and Application.Wait to allow users to bypass any autoroutines

If RunNative = False Then Set XLapp = Nothing  'v4.04

On Error GoTo ErrorHandler  'leaves XLapp <> Nothing on error

If XLpth <> "" Then XLpth = mao_fix_path(XLpth)  'v3.13

Dim WB As Object  'Excel.Workbook  v3.15
Set run_Excel = XLlaunch(LeaveOpenWhenDone, RunNative) 'v4.03

With run_Excel
    .Visible = bForceVisibility  'v4.07
    .DisplayAlerts = False
    If XLmacro <> vbNullString Then
    'open WB, run macro
        Set WB = .Workbooks.Open(XLpth & XLfn)
        .Run XLmacro
    On Error Resume Next  'prevents errors where ThisWorbook closes automatically
        If LeaveOpenWhenDone = False Then .Close SaveChanges:=True
    On Error GoTo 0
    Else
    'macro(s) will autorun on Workbook.Open
        If SP_domain <> "" And (InStr(XLpth, SP_domain) > 0 Or InStr(XLfn, SP_domain) > 0) Then
        'always open RO from SharePoint (prevents issues with CheckIn and can dictate automation when opened Read-Only)
            .Workbooks.Open FileName:=XLpth & XLfn, ReadOnly:=True
        Else
        'always open writeable from shared drive (unless rxlOpenReadOnly is specified)
            .Workbooks.Open FileName:=XLpth & XLfn, ReadOnly:=rxlOpenReadOnly
        End If
    On Error Resume Next  'prevents errors where ThisWorbook closes automatically
        If LeaveOpenWhenDone = True Then .ActiveWorkbook.Close SaveChanges:=True
    On Error GoTo 0
    End If
    .DisplayAlerts = False
    On Error Resume Next  'prevents errors where Excel closes automatically
        If LeaveOpenWhenDone = False Then
            .Quit
            Set run_Excel = Nothing
        Else
            .Visible = True
        End If
    On Error GoTo 0
End With
Exit Function 'v4.08

ErrorHandler:
On Error Resume Next  'v4.08
run_Excel.Visible = True  'v4.07
Set XLapp = run_Excel

End Function

Function XLlaunch(Optional ByVal bForceVisibility As Boolean = maoVisibleByDefault _
    , Optional RunNative As Boolean = False) _
    As Object
'v4.07 2013-11-04 14:04

Dim InXL As Boolean
If InStr(Application.Name, "Excel") > 0 Then InXL = True 'v3.12

If RunNative = True And InXL = True Then
    Set XLlaunch = Application
Else
    Set XLlaunch = CreateObject("Excel.Application")
    XLlaunch.Visible = bForceVisibility
End If

End Function

Function mao_fix_path(ByVal pth As String) As String
'v4.06 2013-10-31 11:32
'adds the relevant last slash to the path, if missing, and if not including .accdb/.mdb/.xl

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

If InStr(pth, ".xl") > 0 Or InStr(pth, ".accdb") > 0 Or InStr(pth, ".mdb") > 0 Then
    mao_fix_path = pth
ElseIf InStr(pth, cBsl) > 0 And Right(pth, 1) <> cBsl Then
    mao_fix_path = pth & cBsl
ElseIf InStr(pth, cFsl) > 0 And Right(pth, 1) <> cFsl Then
    mao_fix_path = pth & cFsl
Else
    mao_fix_path = pth
End If

End Function



'Sub runOutlook_SendMail(sSubject As String, sRecipient As String _
'    , sBodyText As String, DisplayFirst As Boolean _
'    , Optional sAttach_pthfn1 As String _
'    , Optional sAttach_pthfn2 As String)
'retired v4.01 2013-08-21 16:27
'
'legacy code:
'modEmail.SendEmail sRecipient, "", "", sSubject, sBodyText, DisplayFirst, sAttach_pthfn1
'
''v3.15 2013-07-10 12:25
''*** DOESN'T WORK IN OFFICE 2003? *** tested OK in 2010
'' - haven't bypassed Outlook virus protection so will ask for permission?
'' - can only attach max 2 files, need to change this to allow more
'
''Syntax:
''  SendMail "My email with attachment", "name@host.com", "Here is an email", False, "c:\test.txt"
''source: http://www.vbaexpress.com/kb/getarticle.php?kb_id=758
'
'    Dim olMail As Object  'Outlook.MailItem
'    Dim blRunning As Boolean
'
'     'get application
'    blRunning = True
'    On Error Resume Next
'    Set OLapp = GetObject(, "Outlook.Application")
'    If OLapp Is Nothing Then
'        Set OLapp = CreateObject("Outlook.Application")
'        blRunning = False
'    End If
'    On Error GoTo 0
'
'    Set olMail = OLapp.CreateItem(0)  '0=olMailItem, see http://www.ozgrid.com/forum/showthread.php?t=148735
'    With olMail
'         'Specify the email subject
'        .Subject = sSubject
'         'Specify who it should be sent to
'         'Repeat this line to add further recipients
'        .Recipients.Add sRecipient
'         'specify the file to attach
'         'repeat this line to add further attachments
'        If sAttach_pthfn1 <> vbNullString Then
'            .Attachments.Add sAttach_pthfn1
'            If sAttach_pthfn2 <> vbNullString Then
'                .Attachments.Add sAttach_pthfn2
'            End If
'        End If
'         'specify the text to appear in the email
'        .Body = sBodyText
'         'Choose which of the following 2 lines to have commented out
'        If DisplayFirst = True Then
'            .Display 'This will display the message for you to check and send yourself
'        Else
'            .Send ' This will send the message straight away
'        End If
'    End With
'
'    If Not blRunning Then OLapp.Quit
'
'    Set OLapp = Nothing
'    Set olMail = Nothing
'
'End Sub

VBA Modules: modSpecialFolders v1.00a

Read this for full information on these modules

modSpecialFolders reports back the path for a variety of "special folders" from Windows Shell32.  It's fairly standard and extremely useful code, great for deducing where to save output files and such.

This code is copied from Microsoft support site, I've just added my own annotations.

'modSpecialFolders
'v1.00a 2013-07-19 11:55
'always export to \\GBMNCWSA050\BPMpublic\VBA Modules\

'===========================================================================
' modSpecialFolders
'===========================================================================
'   Finds path to various "special folders" in Windows
'   e.g. My Documents, Program Files, Shared Documents, etc.

'===========================================================================
' Additional modules required:
'===========================================================================
'   None

'===========================================================================
' Additional References required:
'===========================================================================
'   None

'===========================================================================
' External applications required:
'===========================================================================
'   None

'=========================================================================
' VERSION HISTORY
'=========================================================================
'   v1.00a  annotations only
'   v1.00   created from source:
'           http://answers.microsoft.com/en-us/office/forum/office_2010-customize/how-2-refer-to-desktop/97eba910-54c9-409f-9454-6d7c8d54d009



Option Explicit

Public Declare Function SHGetSpecialFolderLocation _
                         Lib "shell32" (ByVal hwnd As Long, _
                                        ByVal nFolder As Long, ppidl As Long) As Long

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

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

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

Public 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

VBA Modules: Excel: xlShellAndWait v1.00aa

Read this for full information on these modules

This code is copied exactly from Chip Pearson's modShellAndWait code.

v2.00 has late binding so it can be used outside of Excel

'xlShellAndWait
'v2.00 2013-12-17 15:58
'always export to \\GBMNCWSA050\BPMpublic\VBA Modules\

'===========================================================================
' xlShellAndWait
'===========================================================================
'   Based on original code in modShellAndWait by Chip Pearson
'
'   See below for original code & info, this version is renamed to prevent
'   accidental use in other Office applications (it only works in Excel)

'===========================================================================
' Additional modules required:
'===========================================================================
'   None

'===========================================================================
' Additional References required:
'===========================================================================
'   Microsoft Excel Object Library (if not running from Excel)

'===========================================================================
' External applications required:
'===========================================================================
'   None

'=========================================================================
' VERSION HISTORY
'=========================================================================
'   v2.00   late binding
'   v1.00aa annotations only
'   v1.00a  annotations only
'   v1.00   original code from source

'===========================================================================
' HELP CONTACT
'===========================================================================
'   Tom Parish  tom.parish@dhl.com  +44 161 772 7359
'   Performance Measurement Analyst for Business Performance Measurement (GB)
'   DHL Global Forwarding (UK) Limited
'   Registered in England, registered number 4056042
'   Registered Office: Magna House, 18-32 London Road, Staines TW18 4BP
'=========================================================================

Option Explicit
Option Compare Text

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' modShellAndWait
' By Chip Pearson, chip@cpearson.com, www.cpearson.com
' This page on the web site: www.cpearson.com/Excel/ShellAndWait.aspx
' 9-September-2008
'
' This module contains code for the ShellAndWait function that will Shell to a process
' and wait for that process to end before returning to the caller.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
    ByVal hHandle As Long, _
    ByVal dwMilliSeconds As Long) As Long

Private Declare Function OpenProcess Lib "kernel32.dll" ( _
    ByVal dwDesiredAccess As Long, _
    ByVal bInheritHandle As Long, _
    ByVal dwProcessId As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" ( _
    ByVal hObject As Long) As Long

Private Const SYNCHRONIZE = &H100000

Public Enum ShellAndWaitResult
    Success = 0
    Failure = 1
    TimeOut = 2
    InvalidParameter = 3
    SysWaitAbandoned = 4
    UserWaitAbandoned = 5
    UserBreak = 6
End Enum

Public Enum ActionOnBreak
    IgnoreBreak = 0
    AbandonWait = 1
    PromptUser = 2
End Enum

Private Const STATUS_ABANDONED_WAIT_0 As Long = &H80
Private Const STATUS_WAIT_0 As Long = &H0
Private Const WAIT_ABANDONED As Long = (STATUS_ABANDONED_WAIT_0 + 0)
Private Const WAIT_OBJECT_0 As Long = (STATUS_WAIT_0 + 0)
Private Const WAIT_TIMEOUT As Long = 258&
Private Const WAIT_FAILED As Long = &HFFFFFFFF
Private Const WAIT_INFINITE = -1&


Public Function ShellAndWait(ShellCommand As String, _
                    TimeOutMs As Long, _
                    ShellWindowState As VbAppWinStyle, _
                    BreakKey As ActionOnBreak) As ShellAndWaitResult
'v2.00 2013-12-17 15:58 - late binding for non-Excel Application use
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' ShellAndWait
'
' This function calls Shell and passes to it the command text in ShellCommand. The function
' then waits for TimeOutMs (in milliseconds) to expire.
'
'   Parameters:
'       ShellCommand
'           is the command text to pass to the Shell function.
'
'       TimeOutMs
'           is the number of milliseconds to wait for the shell'd program to wait. If the
'           shell'd program terminates before TimeOutMs has expired, the function returns
'           ShellAndWaitResult.Success = 0. If TimeOutMs expires before the shell'd program
'           terminates, the return value is ShellAndWaitResult.TimeOut = 2.
'
'       ShellWindowState
'           is an item in VbAppWinStyle specifying the window state for the shell'd program.
'
'       BreakKey
'           is an item in ActionOnBreak indicating how to handle the application's cancel key
'           (Ctrl Break). If BreakKey is ActionOnBreak.AbandonWait and the user cancels, the
'           wait is abandoned and the result is ShellAndWaitResult.UserWaitAbandoned = 5.
'           If BreakKey is ActionOnBreak.IgnoreBreak, the cancel key is ignored. If
'           BreakKey is ActionOnBreak.PromptUser, the user is given a ?Continue? message. If the
'           user selects "do not continue", the function returns ShellAndWaitResult.UserBreak = 6.
'           If the user selects "continue", the wait is continued.
'
'   Return values:
'            ShellAndWaitResult.Success = 0
'               indicates the the process completed successfully.
'            ShellAndWaitResult.Failure = 1
'               indicates that the Wait operation failed due to a Windows error.
'            ShellAndWaitResult.TimeOut = 2
'               indicates that the TimeOutMs interval timed out the Wait.
'            ShellAndWaitResult.InvalidParameter = 3
'               indicates that an invalid value was passed to the procedure.
'            ShellAndWaitResult.SysWaitAbandoned = 4
'               indicates that the system abandoned the wait.
'            ShellAndWaitResult.UserWaitAbandoned = 5
'               indicates that the user abandoned the wait via the cancel key (Ctrl+Break).
'               This happens only if BreakKey is set to ActionOnBreak.AbandonWait.
'            ShellAndWaitResult.UserBreak = 6
'               indicates that the user broke out of the wait after being prompted with
'               a ?Continue message. This happens only if BreakKey is set to
'               ActionOnBreak.PromptUser.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Dim TaskID As Long
Dim ProcHandle As Long
Dim WaitRes As Long
Dim Ms As Long
Dim MsgRes As VbMsgBoxResult
Dim SaveCancelKey As XlEnableCancelKey  'NB: only works in Excel
Dim ElapsedTime As Long
Dim Quit As Boolean
Const ERR_BREAK_KEY = 18
Const DEFAULT_POLL_INTERVAL = 500
Dim XLapp As Object  'v2.00
If InStr(Application.Name, "Excel") > 0 Then Set XLapp = Application Else Set XLapp = CreateObject("Excel.Application")

If Trim(ShellCommand) = vbNullString Then
    ShellAndWait = ShellAndWaitResult.InvalidParameter
    Exit Function
End If

If TimeOutMs < 0 Then
    ShellAndWait = ShellAndWaitResult.InvalidParameter
    Exit Function
ElseIf TimeOutMs = 0 Then
    Ms = WAIT_INFINITE
Else
    Ms = TimeOutMs
End If

Select Case BreakKey
    Case AbandonWait, IgnoreBreak, PromptUser
        ' valid
    Case Else
        ShellAndWait = ShellAndWaitResult.InvalidParameter
        Exit Function
End Select

Select Case ShellWindowState
    Case vbHide, vbMaximizedFocus, vbMinimizedFocus, vbMinimizedNoFocus, vbNormalFocus, vbNormalNoFocus
        ' valid
    Case Else
        ShellAndWait = ShellAndWaitResult.InvalidParameter
        Exit Function
End Select

On Error Resume Next
Err.Clear
TaskID = Shell(ShellCommand, ShellWindowState)
If (Err.Number <> 0) Or (TaskID = 0) Then
    ShellAndWait = ShellAndWaitResult.Failure
    Exit Function
End If

ProcHandle = OpenProcess(SYNCHRONIZE, False, TaskID)
If ProcHandle = 0 Then
    ShellAndWait = ShellAndWaitResult.Failure
    Exit Function
End If


On Error GoTo ErrH:
SaveCancelKey = XLapp.EnableCancelKey
XLapp.EnableCancelKey = xlErrorHandler
WaitRes = WaitForSingleObject(ProcHandle, DEFAULT_POLL_INTERVAL)
Do Until WaitRes = WAIT_OBJECT_0
    DoEvents
    Select Case WaitRes
        Case WAIT_ABANDONED
            ' Windows abandoned the wait
            ShellAndWait = ShellAndWaitResult.SysWaitAbandoned
            Exit Do
        Case WAIT_OBJECT_0
            ' Successful completion
            ShellAndWait = ShellAndWaitResult.Success
            Exit Do
        Case WAIT_FAILED
            ' attach failed
            ShellAndWait = ShellAndWaitResult.Success
            Exit Do
        Case WAIT_TIMEOUT
            ' Wait timed out. Here, this time out is on DEFAULT_POLL_INTERVAL.
            ' See if ElapsedTime is greater than the user specified wait
            ' time out. If we have exceed that, get out with a TimeOut status.
            ' Otherwise, reissue as wait and continue.
            ElapsedTime = ElapsedTime + DEFAULT_POLL_INTERVAL
            If Ms > 0 Then
                ' user specified timeout
                If ElapsedTime > Ms Then
                    ShellAndWait = ShellAndWaitResult.TimeOut
                    Exit Do
                Else
                    ' user defined timeout has not expired.
                End If
            Else
                ' infinite wait -- do nothing
            End If
            ' reissue the Wait on ProcHandle
            WaitRes = WaitForSingleObject(ProcHandle, DEFAULT_POLL_INTERVAL)
          
        Case Else
            ' unknown result, assume failure
            ShellAndWait = ShellAndWaitResult.Failure
            Quit = True
    End Select
Loop

CloseHandle ProcHandle
XLapp.EnableCancelKey = SaveCancelKey
Exit Function

ErrH:
Debug.Print "ErrH: Cancel: " & XLapp.EnableCancelKey
If Err.Number = ERR_BREAK_KEY Then
    If BreakKey = ActionOnBreak.AbandonWait Then
        CloseHandle ProcHandle
        ShellAndWait = ShellAndWaitResult.UserWaitAbandoned
        XLapp.EnableCancelKey = SaveCancelKey
        Set XLapp = Nothing
        Exit Function
    ElseIf BreakKey = ActionOnBreak.IgnoreBreak Then
        Err.Clear
        Resume
    ElseIf BreakKey = ActionOnBreak.PromptUser Then
        MsgRes = MsgBox("User Process Break." & vbCrLf & _
            "Continue to wait?", vbYesNo)
        If MsgRes = vbNo Then
            CloseHandle ProcHandle
            ShellAndWait = ShellAndWaitResult.UserBreak
            XLapp.EnableCancelKey = SaveCancelKey
        Else
            Err.Clear
            Resume Next
        End If
    Else
        'Debug.Print "Unknown value of 'BreakKey': " & CStr(BreakKey)
        CloseHandle ProcHandle
        XLapp.EnableCancelKey = SaveCancelKey
        ShellAndWait = ShellAndWaitResult.Failure
    End If
Else
    ' some other error. assume failure
    CloseHandle ProcHandle
    ShellAndWait = ShellAndWaitResult.Failure
End If

XLapp.EnableCancelKey = SaveCancelKey
Set XLapp = Nothing

End Function

VBA Modules: modZip v8.04

Read this for full information on these modules

This particular module is very useful, uses the excellent 7zip command line application to automatically zip or unzip any type of file.  It will work with URL source/targets, but always more reliable with UNC.
The Zip7sub macro will "install" 7zip automatically if you save 7za.exe somewhere publicly accessible on your network and specify the location in the code below.  Note that the "iShare" UNC address I've used is not accessible to the public, so you just have to replace the location with one you can access.   It can of course also be any accessible location on your own machine, e.g. your Desktop or Documents or Downloads folder.

This module makes use of ShellAndWait by Chip Pearson and modSpecialFolders -- standard function code from the Microsoft support site.  Code from both is included within this module.

v8 adds z7_Force_Connection -- adapted from the xlSharePoint module.  Highly recommended to run this before zipping to SharePoint locations.

If you're running this from Access or Outlook, it'll work fine, but you should enable Excel Object Library via Tools > References to prevent Debug > Compile errors with a couple of Excel Application commands.

As it says below, it should still work in XP, but I've not tested it since I updated the code for Windows 7.

'modZip
'v8.04 2014-01-10 14:27

'===========================================================================
' 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
'=========================================================================

'************************************************************
'*****  WARNING: SETTINGS BELOW MAY NEED TO BE AMENDED  *****
'************************************************************

'===========================================================================
' modZip
'===========================================================================
'   Various zipping functions.  Uses 7zip Command Line Utility (7za.exe)
'   7zip is free for personal or business use   http://www.7zip.com/
'
'   Module self-installs (copies) 7zip from shared drive or SharePoint
'   location(s) to [C:\Users\[userID]\Documents]\[zSubFolder]\[z7pth]\
'
'   Can easily be adapted to use WinZip or any command line utility,
'   but pay careful attention to app-specific command line syntax.
'
'   Syntax to zip file(s):
'       Zip7Sub [FilesPathFn], [ArchivePathFn], True
'
'   Also see:
'       Sub syntax_to_zip_one_file()

'===========================================================================
' Additional modules required:
'===========================================================================
'   None
'
'   Code included from other modules:
'      [modSpecialFolders]
'      [xlShellAndWait]

'===========================================================================
' Additional References required:
'===========================================================================
'   Microsoft Excel Object Library (if not running from Excel)

'===========================================================================
' External applications required:
'===========================================================================
'   None

'=========================================================================
' VERSION HISTORY
'=========================================================================
'v8.04 2014-01-10 14:27 - Z7_Force_Connection - bugfix for zips (remove "" from ends)
'v8.03 2014-01-09 18:13 - Z7_CloseExplorerWindow - bugfix to prevent errors with Internet Explorer shell windows
'v8.02 2014-01-09 17:10 - Z7_CloseExplorerWindow - bugfix to prevent blank folder name
'v8.01 2014-01-09 15:46 - Z7_Force_Connection - added bExplorerKill failsafe option to kill all Explorer windows (prevents leaving countless processes open - although rarely triggered)
'v8.00 2014-01-10 10:28 - Z7_Force_Connection - checks/forces connection (to SharePoint) adapted from xlSharePoint v5.03
'   v7.01   syntax_to_zip_one_file: zips .rwz file first (Outlook Rules Wizard)
'   v7.01   syntax_to_zip_one_file: also zips .docx files
'   v7.00   ZIP_EXE_pthfn: rebuild, uses secondary/tertiary install locations
'   v6.00   included code from xlShellAndWait and modSpecialFolders
'   v5.03   syntax_to_zip_one_file: improved validity check in non-Excel apps
'   v5.02   syntax_to_zip_one_file: msgbox added
'   v5.01   ZIP_EXE_copy: added z7exn
'   v5.00a  annotations only
'   v5.00   WINDOWS 7 ONLY: moved to C:\Users\All Users\BPM Tools\
'           zlmax set to 2, now only refers to iShare sources (UNC then URL)
'   v4.02   syntax_to_zip_one_file: now sources from GBMNCWSA050
'   v4.01   bugfix: Application.Statusbar in non-Excel applications
'           bugfix: xlShellAndWait is only possible in Excel
'   v4.00   Zip7Sub: added zKillFirst and zRecurse options
'           ZIP_EXE_copy: process massively improved
'           annotations improved
'   v3.08   bugfix

Option Explicit

'************************************************************
'*****  WARNING: SETTINGS BELOW MAY NEED TO BE AMENDED  *****
'************************************************************

'zlmax = possible source locations in scope, MAX 2 in code  v5.00
    Private Const zlmax As Byte = 2  'v5.00
'users must have access to shared drive and SharePoint UNC/URL locations (read-only)
'NB: SharePoint cannot host exe files, so remove file extension, ".exe" will be renamed during FileCopy
    Private Const z7e1 As String = "\\ishare.dhl.com\sites\DGFUK\BPMpublic\Resources\"                  'v5.00
    Private Const z7e2 As String = "http://ishare.dhl.com/sites/DGFUK/BPMpublic/Resources/"             'v5.00
'subfolders in local [C:\Users]\All Users\ folder to create for copying 7za.exe (can be "")
    Private Const zSubFolder As String = "BPM Tools"  'use backslash to specify 2 deep e.g. "SubFolder1\SubFolder2"
    Private Const z7pth As String = "7z"        'subfolder for 7zip                                     'v5.01
'possible 7zip executable filenames
    Private Const z7exe As String = "7za.exe"   'any location where .exe is permitted                   'v5.01
    Private Const z7ext As String = "7za.ext"   'any location where .exe forbidden                      'v5.01
    Private Const z7exn As String = "7za"       'any location where .exe forbidden, without extension   'v5.01

'source: http://www.vboffice.net/sample.html?mnu=2&lang=en&smp=56&cmd=showitem&pub=6
    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliSeconds As Long)
    Private Const zSleepTime As Long = 10000  'v5.03, time in ms, i.e. 10000 = 10s

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

'
'   Code from xlShellAndWait module:
'

Option Compare Text

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' modShellAndWait
' By Chip Pearson, chip@cpearson.com, www.cpearson.com
' This page on the web site: www.cpearson.com/Excel/ShellAndWait.aspx
' 9-September-2008
'
' This module contains code for the ShellAndWait function that will Shell to a process
' and wait for that process to end before returning to the caller.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
    ByVal hHandle As Long, _
    ByVal dwMilliSeconds As Long) As Long

Private Declare Function OpenProcess Lib "kernel32.dll" ( _
    ByVal dwDesiredAccess As Long, _
    ByVal bInheritHandle As Long, _
    ByVal dwProcessId As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" ( _
    ByVal hObject As Long) As Long

Private Const SYNCHRONIZE = &H100000

Private Enum ShellAndWaitResult
    Success = 0
    Failure = 1
    TimeOut = 2
    InvalidParameter = 3
    SysWaitAbandoned = 4
    UserWaitAbandoned = 5
    UserBreak = 6
End Enum

Private Enum ActionOnBreak
    IgnoreBreak = 0
    AbandonWait = 1
    PromptUser = 2
End Enum

Private Const STATUS_ABANDONED_WAIT_0 As Long = &H80
Private Const STATUS_WAIT_0 As Long = &H0
Private Const WAIT_ABANDONED As Long = (STATUS_ABANDONED_WAIT_0 + 0)
Private Const WAIT_OBJECT_0 As Long = (STATUS_WAIT_0 + 0)
Private Const WAIT_TIMEOUT As Long = 258&
Private Const WAIT_FAILED As Long = &HFFFFFFFF
Private Const WAIT_INFINITE = -1&



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

'
'   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

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

Private Function ShellAndWait(ShellCommand As String, _
                    TimeOutMs As Long, _
                    ShellWindowState As VbAppWinStyle, _
                    BreakKey As ActionOnBreak) As ShellAndWaitResult
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' ShellAndWait
'
' This function calls Shell and passes to it the command text in ShellCommand. The function
' then waits for TimeOutMs (in milliseconds) to expire.
'
'   Parameters:
'       ShellCommand
'           is the command text to pass to the Shell function.
'
'       TimeOutMs
'           is the number of milliseconds to wait for the shell'd program to wait. If the
'           shell'd program terminates before TimeOutMs has expired, the function returns
'           ShellAndWaitResult.Success = 0. If TimeOutMs expires before the shell'd program
'           terminates, the return value is ShellAndWaitResult.TimeOut = 2.
'
'       ShellWindowState
'           is an item in VbAppWinStyle specifying the window state for the shell'd program.
'
'       BreakKey
'           is an item in ActionOnBreak indicating how to handle the application's cancel key
'           (Ctrl Break). If BreakKey is ActionOnBreak.AbandonWait and the user cancels, the
'           wait is abandoned and the result is ShellAndWaitResult.UserWaitAbandoned = 5.
'           If BreakKey is ActionOnBreak.IgnoreBreak, the cancel key is ignored. If
'           BreakKey is ActionOnBreak.PromptUser, the user is given a ?Continue? message. If the
'           user selects "do not continue", the function returns ShellAndWaitResult.UserBreak = 6.
'           If the user selects "continue", the wait is continued.
'
'   Return values:
'            ShellAndWaitResult.Success = 0
'               indicates the the process completed successfully.
'            ShellAndWaitResult.Failure = 1
'               indicates that the Wait operation failed due to a Windows error.
'            ShellAndWaitResult.TimeOut = 2
'               indicates that the TimeOutMs interval timed out the Wait.
'            ShellAndWaitResult.InvalidParameter = 3
'               indicates that an invalid value was passed to the procedure.
'            ShellAndWaitResult.SysWaitAbandoned = 4
'               indicates that the system abandoned the wait.
'            ShellAndWaitResult.UserWaitAbandoned = 5
'               indicates that the user abandoned the wait via the cancel key (Ctrl+Break).
'               This happens only if BreakKey is set to ActionOnBreak.AbandonWait.
'            ShellAndWaitResult.UserBreak = 6
'               indicates that the user broke out of the wait after being prompted with
'               a ?Continue message. This happens only if BreakKey is set to
'               ActionOnBreak.PromptUser.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Dim TaskID As Long
Dim ProcHandle As Long
Dim WaitRes As Long
Dim Ms As Long
Dim MsgRes As VbMsgBoxResult
Dim SaveCancelKey As XlEnableCancelKey  'NB: only works in Excel
Dim ElapsedTime As Long
Dim Quit As Boolean
Const ERR_BREAK_KEY = 18
Const DEFAULT_POLL_INTERVAL = 500

If Trim(ShellCommand) = vbNullString Then
    ShellAndWait = ShellAndWaitResult.InvalidParameter
    Exit Function
End If

If TimeOutMs < 0 Then
    ShellAndWait = ShellAndWaitResult.InvalidParameter
    Exit Function
ElseIf TimeOutMs = 0 Then
    Ms = WAIT_INFINITE
Else
    Ms = TimeOutMs
End If

Select Case BreakKey
    Case AbandonWait, IgnoreBreak, PromptUser
        ' valid
    Case Else
        ShellAndWait = ShellAndWaitResult.InvalidParameter
        Exit Function
End Select

Select Case ShellWindowState
    Case vbHide, vbMaximizedFocus, vbMinimizedFocus, vbMinimizedNoFocus, vbNormalFocus, vbNormalNoFocus
        ' valid
    Case Else
        ShellAndWait = ShellAndWaitResult.InvalidParameter
        Exit Function
End Select

On Error Resume Next
Err.Clear
TaskID = Shell(ShellCommand, ShellWindowState)
If (Err.Number <> 0) Or (TaskID = 0) Then
    ShellAndWait = ShellAndWaitResult.Failure
    Exit Function
End If

ProcHandle = OpenProcess(SYNCHRONIZE, False, TaskID)
If ProcHandle = 0 Then
    ShellAndWait = ShellAndWaitResult.Failure
    Exit Function
End If


On Error GoTo ErrH:
SaveCancelKey = Application.EnableCancelKey
Application.EnableCancelKey = xlErrorHandler
WaitRes = WaitForSingleObject(ProcHandle, DEFAULT_POLL_INTERVAL)
Do Until WaitRes = WAIT_OBJECT_0
    DoEvents
    Select Case WaitRes
        Case WAIT_ABANDONED
            ' Windows abandoned the wait
            ShellAndWait = ShellAndWaitResult.SysWaitAbandoned
            Exit Do
        Case WAIT_OBJECT_0
            ' Successful completion
            ShellAndWait = ShellAndWaitResult.Success
            Exit Do
        Case WAIT_FAILED
            ' attach failed
            ShellAndWait = ShellAndWaitResult.Success
            Exit Do
        Case WAIT_TIMEOUT
            ' Wait timed out. Here, this time out is on DEFAULT_POLL_INTERVAL.
            ' See if ElapsedTime is greater than the user specified wait
            ' time out. If we have exceed that, get out with a TimeOut status.
            ' Otherwise, reissue as wait and continue.
            ElapsedTime = ElapsedTime + DEFAULT_POLL_INTERVAL
            If Ms > 0 Then
                ' user specified timeout
                If ElapsedTime > Ms Then
                    ShellAndWait = ShellAndWaitResult.TimeOut
                    Exit Do
                Else
                    ' user defined timeout has not expired.
                End If
            Else
                ' infinite wait -- do nothing
            End If
            ' reissue the Wait on ProcHandle
            WaitRes = WaitForSingleObject(ProcHandle, DEFAULT_POLL_INTERVAL)
           
        Case Else
            ' unknown result, assume failure
            ShellAndWait = ShellAndWaitResult.Failure
            Quit = True
    End Select
Loop

CloseHandle ProcHandle
Application.EnableCancelKey = SaveCancelKey
Exit Function

ErrH:
Debug.Print "ErrH: Cancel: " & Application.EnableCancelKey
If Err.Number = ERR_BREAK_KEY Then
    If BreakKey = ActionOnBreak.AbandonWait Then
        CloseHandle ProcHandle
        ShellAndWait = ShellAndWaitResult.UserWaitAbandoned
        Application.EnableCancelKey = SaveCancelKey
        Exit Function
    ElseIf BreakKey = ActionOnBreak.IgnoreBreak Then
        Err.Clear
        Resume
    ElseIf BreakKey = ActionOnBreak.PromptUser Then
        MsgRes = MsgBox("User Process Break." & vbCrLf & _
            "Continue to wait?", vbYesNo)
        If MsgRes = vbNo Then
            CloseHandle ProcHandle
            ShellAndWait = ShellAndWaitResult.UserBreak
            Application.EnableCancelKey = SaveCancelKey
        Else
            Err.Clear
            Resume Next
        End If
    Else
        'Debug.Print "Unknown value of 'BreakKey': " & CStr(BreakKey)
        CloseHandle ProcHandle
        Application.EnableCancelKey = SaveCancelKey
        ShellAndWait = ShellAndWaitResult.Failure
    End If
Else
    ' some other error. assume failure
    CloseHandle ProcHandle
    ShellAndWait = ShellAndWaitResult.Failure
End If

Application.EnableCancelKey = SaveCancelKey

End Function

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

Function Zip7Sub(ByVal zPathFiles As String, ByVal zArchive As String _
    , ByVal ZIP_IT As Boolean, Optional ByVal zKillFirst As Boolean = False _
    , Optional ByVal zRecurse As Boolean = False) As Byte
'v8.00 2014-01-08 10:37 - added Z7_Force_Connection
'v6.00 2013-08-08 10:48
'find/setup 7zip on user's PC, and zip/unzip files
'original source:  http://vb-helper.com/howto_shell_zip_and_unzip.html

Dim ZIP_EXE As String, ZIP_CMD As String, ZIP_DEBUG As String, rslt As Byte
Dim qm As String
qm = Chr(34)

'check/force connection to specified path
If Z7_Force_Connection(zArchive) = False Then
'can't connect, don't do it
    rslt = 99
    GoTo ErrorHandler
End If

'get ZIP_EXE, runs very long function, skip this when reviewing code stepwise
ZIP_EXE = ZIP_EXE_copy

If zKillFirst = True Then
    If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = "Deleting existing archive... || " & zArchive
    If Dir(zArchive) <> "" Then Kill zArchive   'write access is required for this location to Kill and then to Zip
    If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = False
End If

ZIP_EXE = qm & ZIP_EXE & qm
zArchive = qm & zArchive & qm
zPathFiles = qm & zPathFiles & qm

Dim zAction As String, zSwitches As String
If ZIP_IT = True Then
'append the source file to the target zip
    'syntax: 7zip a <archive> <sourceFILE>
    zAction = " a "
    If InStr(zPathFiles, ".") = 0 Then Debug.Print "ZIP_CMD: file extension not specified in add path, maybe OK, zPathFiles:=" & zPathFiles & vbLf
    zPathFiles = " " & zPathFiles
    zSwitches = ""
Else
'extract the target zip to the 7z working directory and overwrite existing files
    'syntax: 7zip e <archive> -o<outputPATH> -y
    zAction = " e "
    If InStr(zPathFiles, ".") <> 0 Then ZIP_DEBUG = ZIP_DEBUG & "ZIP_CMD: file extension should not be specified in extract path, maybe OK, zPathFiles:=" & zPathFiles & vbLf
    zPathFiles = " -o" & zPathFiles
    zSwitches = " -y "
End If

If zRecurse = True Then zSwitches = zSwitches & " -r "

'set the command line
ZIP_CMD = ZIP_EXE & zAction & zArchive & zPathFiles & zSwitches
ZIP_DEBUG = ZIP_DEBUG & "ZIP_CMD: [" & ZIP_CMD & "]" & vbLf

'do the zipping
If InStr(Application.Name, "Excel") > 0 Then
'use xlShellAndWait
    Application.StatusBar = "Zipping to " & zArchive
    rslt = val(ShellAndWait(ZIP_CMD, 0, vbHide, PromptUser))
    ZIP_DEBUG = ZIP_DEBUG & "ShellAndWait result: " & rslt
Else
'just use Shell
    Shell ZIP_CMD
End If

ErrorHandler:
'debug only on error
If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = False
If rslt <> 0 Then
    Debug.Print ZIP_DEBUG
    If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = "Zip failed, check Debug window || " & zArchive
End If
Zip7Sub = rslt

End Function

Function ZIP_EXE_copy() As String
'v7.00 2013-08-15 12:58
'installs (copies) 7za.exe from remote location to local folder
'returns path & fn for copied file (or "" if not copied)

If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = "Checking local machine for 7zip..."

Dim ZIP_PTH(0 To zlmax) As String, z As Byte, pp As String
Const tt As String = "Error in macro: ZIP_EXE_copy"
Dim ZIP_EXE As String

'check and/or create local 7zip app location
ZIP_EXE = ZIP_EXE_pthfn(zSubFolder)      'local pth & fn
ZIP_PTH(0) = Replace(ZIP_EXE, z7exe, "") 'local path
'specify shared drive / iShare ZIP_EXE locations
'if ZIP_PTH(0) not found, looks for z7eN (but only if zlmax >= N)
If zlmax >= 1 Then ZIP_PTH(1) = z7e1
If zlmax >= 2 Then ZIP_PTH(2) = z7e2
'If zlmax >= 3 Then ZIP_PTH(3) = z7e3
'If zlmax >= 4 Then ZIP_PTH(4) = z7e4
'If zlmax >= 5 Then ZIP_PTH(5) = z7e5 'can add more if ever needed

'test access to specified ZIP_PTH(z) location
For z = 0 To zlmax
'0 is local location, preferred, only looks for 1 if 0 not found
    If z > 0 Then If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = "Checking remote locations for 7zip || " & ZIP_PTH(z)
On Error Resume Next
    If Dir(ZIP_PTH(z), vbDirectory) = "." Then
    'folder exists, check for z7exe OR z7ext OR z7exn
    'look for z7exe in ZIP_PTH(z)  (usually "7za.exe")
        ZIP_EXE_copy = Dir(ZIP_PTH(z) & z7exe)  'sets this to z7exe if found
        If ZIP_EXE_copy = z7exe Then Exit For
    'look for z7ext in ZIP_PTH(z)  (usually "7za.ext")
        ZIP_EXE_copy = Dir(ZIP_PTH(z) & z7ext)  'sets this to z7ext if found
        If ZIP_EXE_copy = z7ext Then Exit For
    'look for z7exn in ZIP_PTH(z)  (usually "7za")  'v5.01
        ZIP_EXE_copy = Dir(ZIP_PTH(z) & z7exn)  'sets this to z7exn if found
        If ZIP_EXE_copy = z7exn Then Exit For
On Error GoTo 0
    End If
    'folder not found, try the next z
Next z
If ZIP_EXE_copy = vbNullString Then
    pp = "Cannot find 7zip application"
    MsgBox pp, vbCritical, tt
    If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = False
    Exit Function
Else
    If z > 0 And z <= zlmax Then
        If InStr(UCase(Application.Name), UCase("Excel")) > 0 Then Application.StatusBar = "Copying 7zip to local machine..."
        FileCopy ZIP_PTH(z) & ZIP_EXE_copy, ZIP_EXE  'renames to z7exe at destination, regardless of source filename
        If Dir(ZIP_PTH(0) & z7exe) <> z7exe Then
            pp = "Cannot copy 7zip application" & vbLf & "from:  " & vbLf & ZIP_PTH(z) & vbLf & "to:  " & ZIP_PTH(0)
            MsgBox pp, vbCritical, tt
            ZIP_EXE_copy = vbNullString
            If InStr(UCase(Application.Name), UCase("Excel")) > 0 Then Application.StatusBar = False
            Exit Function
        End If
    End If
End If

If InStr(Application.Name, "Excel") > 0 Then Application.StatusBar = False

ZIP_EXE_copy = ZIP_EXE

End Function

Private Function ZIP_EXE_pthfn(Optional zUserSubFolder As String) As String
'v7.00 2013-08-15 12:10
'determines (and creates) usable ZIP_EXE location on user's PC

'for Windows 7:
'   > primary:   C:\Users\All Users\<zUserSubFolder>\7z\
'   > secondary: C:\Users\<userID>\AppData\<zUserSubFolder>\7z\
'   > tertiary:  C:\Users\<userID>\Documents\<zUserSubFolder>\7z\

'for Windows XP:
'   > primary:   C:\Documents and Settings\All Users\Application Data\<zUserSubFolder>\7z\
'   > secondary: C:\Documents and Settings\<userID>\Application Data\<zUserSubFolder>\7z\
'   > tertiary:  C:\Documents and Settings\<userID>\My Documents\<zUserSubFolder>\7z\

Dim objFolders As Object, pth As String, fn As String
Set objFolders = CreateObject("WScript.Shell").SpecialFolders

Dim zInstallPath As String

On Error Resume Next
'primary:    \All Users\AppData\
    zInstallPath = zpth_sl(SpecFolder(CSIDL_COMMON_APPDATA))
    If zTestFolder(zInstallPath) = True Then GoTo zContinue
'secondary:  \<userID>\AppData\
    zInstallPath = zpth_sl(SpecFolder(CSIDL_APPDATA))
    If zTestFolder(zInstallPath) = True Then GoTo zContinue
'tertiary:   \<userID>\Documents\
    zInstallPath = zpth_sl(SpecFolder(CSIDL_PERSONAL))
    If zTestFolder(zInstallPath) = True Then GoTo zContinue

zContinue:
On Error GoTo 0

If zUserSubFolder <> vbNullString Then
'user specified subfolder, clean it first
    zInstallPath = zInstallPath & zpth_sl(zfn_val(zUserSubFolder))   'removes special chars EXCEPT \
Else
'user didn't specify subfolder, use default Private Const zSubFolder
    zInstallPath = zInstallPath & zpth_sl(zfn_val(zSubFolder))  'removes special chars EXCEPT \
End If
If Dir(zInstallPath, vbDirectory) <> "." Then MkDir zInstallPath

zInstallPath = zInstallPath & zpth_sl(z7pth)
If Dir(zInstallPath, vbDirectory) <> "." Then MkDir zInstallPath

ZIP_EXE_pthfn = zInstallPath & z7exe

Set objFolders = Nothing

End Function

Function zTestFolder(zTestPath) As Boolean
'v7.00 2013-08-15 11:48
'tests whether zTestPath is writeable by user

Const zt As String = "ztest"
Dim zp As String
zp = zpth_sl(zTestPath) & zpth_sl(zt)
On Error Resume Next
    MkDir zp
    If Dir(zp, vbDirectory) = "." Then zTestFolder = True Else Exit Function
    If zTestFolder = True Then RmDir zTestPath 'NB: only removes if empty
On Error GoTo 0

End Function

Function zpth_sl(ByVal PathToAddSlash As String, Optional DoURL As Boolean) As String
'v6.01 2013-08-15 11:37
'copied from CIRFiShare v2.36 2012-11-09 12:40
'adds a slash to end of path as required
'DoURL tries to force UNC but will be overridden if URL is 'detected'

Const cFsl As String = "/"  'URL
Const cBsl As String = "\"  'UNC
Const cURL As String = "http://"

'!! can only specify URL if DoURL=True
If DoURL = True And Left(PathToAddSlash, Len(cURL)) <> cURL Then _
    MsgBox "a URL must be specified if DoURL=True", vbCritical, "error in zpth_sl"

'if Path includes http then override DoURL (NB: fn_ipth will determine UNC or URL)
If DoURL = False And Left(PathToAddSlash, Len(cURL)) = cURL Then _
    DoURL = True

If DoURL = True And Right(PathToAddSlash, 1) <> cFsl Then
    zpth_sl = PathToAddSlash & cFsl
    Exit Function
ElseIf Right(PathToAddSlash, 1) <> cBsl Then
    zpth_sl = PathToAddSlash & cBsl
    Exit Function
Else
    zpth_sl = PathToAddSlash
End If

End Function

Private Function zfn_val(sFileName As String, Optional sReplaceInvalidWith As String = "") As String
'v3.00 2012-11-01 12:05
'Purpose   :    Removes invalid characters from a filename
'Inputs    :    sFileName               The file name to clean the invalid characters from.
'               [sReplaceInvalidWith]   The text to replace any invalid characters with.
'Outputs   :    Returns a valid filename.
'Author    :    Andrew Baker
'Date      :    25/03/2001
'Notes     :    http://www.vbusers.com/code/codeget.asp?ThreadID=578&PostID=1

'NB: modZip version allows backslash
'Const csInvalidChars As String = ":\/?*<>|"""
Const csInvalidChars As String = ":/?*<>|"""

Dim lThisChar As Long
zfn_val = sFileName
'Loop over each invalid character, removing any instances found
For lThisChar = 1 To Len(csInvalidChars)
    zfn_val = Replace$(zfn_val, Mid(csInvalidChars, lThisChar, 1), sReplaceInvalidWith)
Next

End Function

Sub syntax_to_zip_one_file()
'v7.02 2013-11-05 15:53
'this example zips all .bas files to iShare (write access to BPMPrivate is required)

    Dim zsrc() As String, ztgt As String, spth As String, ipth As String, fn As String
   
    spth = "\\GBMNCWSA050\BPMpublic\VBA Modules\" 'v4.02
'On Error Resume Next
'    zsrc = Dir(spth, vbDirectory)
'On Error GoTo 0
    ipth = "\\ishare.dhl.com\sites\DGFUK\BPMpublic\VBA Modules\"
   
    ReDim zsrc(0 To 2) As String
    zsrc(0) = spth & "*.rwz"    'Outlook Rules
    zsrc(1) = spth & "*.docx"   'documentation
    zsrc(2) = spth & "*.bas"    'VB modules
   
    ztgt = ipth & "VBA Modules.zip"

    If Dir(zsrc(0)) = vbNullString Then MsgBox "zsrc not found"
   
    If InStr(Application.Name, "Excel") > 0 Then
    'if running from Excel: ShellAndWaitResult = Zip7Sub() As Byte
        If Zip7Sub(zsrc(0), ztgt, True, True, True) = 0 _
        And Zip7Sub(zsrc(1), ztgt, True, False, True) = 0 _
        And Zip7Sub(zsrc(2), ztgt, True, False, True) = 0 Then
            MsgBox "Success!", vbInformation
        Else
            MsgBox "Failed!", vbExclamation
        End If
    Else
    'don't use xlShellAndWait if not in Excel
On Error Resume Next
        Kill ztgt    'error here means no connection to tgt
        Zip7Sub zsrc(0), ztgt, True, True, True     'kill first
        Zip7Sub zsrc(1), ztgt, True, False, True    'add to zip
        Zip7Sub zsrc(2), ztgt, True, False, True    'add to zip
        Sleep zSleepTime
        If Dir(ztgt) = "" Then
            MsgBox "Failed! (But try a longer zSleepTime before panicking)", vbExclamation
        Else
            MsgBox "Success!", vbInformation
        End If
On Error GoTo 0
    End If

End Sub




Function Z7_Force_Connection(ByVal LocalOrUNC_PathOrFile As String _
    , Optional ByVal bExplorerKill As Boolean = True) As Boolean
'v8.04 2014-01-10 14:27 - bugfix for zips (remove "" from ends)
'v8.01 2014-01-09 15:46 - added bExplorerKill failsafe option to kill all Explorer windows (prevents leaving countless processes open - although rarely triggered)
'v8.00 2014-01-08 10:28 - adapted from xlSharePoint v5.03
'v5.03 2014-01-08 10:24 - now accepts filenames at end of path
'v5.02 2013-12-18 10:28 - added option for default UNCpath
'v5.00 2013-12-02 16:17
'NB: not suitable for end user processes, may close ALL instances of Windows Explorer (file browser)
'1. launches UNC in Explorer window
'2. tries to close Explorer
'3. if 2 unsuccessful, kills all instances of Explorer then relaunches Taskbar

'remove file name from pthfn and extract last folder name for Windows Explorer title bar
Dim p As String, f As String, b As Integer, s() As Integer, c As Byte
p = Replace(LocalOrUNC_PathOrFile, Chr(34), "")
b = InStr(p, "\")
While b > 0
    c = c + 1  'count slashes
    ReDim Preserve s(1 To c) As Integer  'add another slash character count
    s(c) = b
    b = InStr(s(c) + 1, p, "\")
Wend
If c > 0 Then
    p = Left(p, s(c))   'full 'root' path without last filename (or folder name) so "C:\Folder\Filename.txt" > "C:\Folder\"
    f = Mid(p, s(c - 1) + 1, s(c) - 1 - s(c - 1))   'folder name (in Explorer title bar) so "C:\Folder\Filename.txt" > "Folder"
End If

Z7_Force_Connection = True

'easy option first, see if UNC already connected
On Error Resume Next
Dim testfn As String
testfn = Dir(p, vbDirectory)
If testfn = "." Then Exit Function
On Error GoTo 0

'open UNC in Explorer, try to close specific Explorer window
ShellAndWait "explorer " & p, 10000, vbHide, AbandonWait
If Z7_CloseExplorerWindow(f) = False Then  'v5.00
'couldn't kill this specific opened Explorer task window
    If bExplorerKill Then  'v8.01
    'use brute force, close all Explorer windows, then relaunch Taskbar
        ShellAndWait "TaskKill /F /IM ""explorer.exe""", 1000, vbHide, AbandonWait
        Shell "C:\Windows\explorer.exe"
    End If
End If

'test UNC connection
On Error Resume Next
testfn = Dir(p, vbDirectory)
If testfn <> "." Then Z7_Force_Connection = False
On Error GoTo 0

End Function

Function Z7_CloseExplorerWindow(ByVal sCurrentFolderName As String) As Boolean
'v8.03 2014-01-09 18:12 - bugfix
'v8.02 2014-01-09 17:10 - bugfix
'v8.00 2014-01-08 10:09 - adapted from xlSharePoint v5.03
'v5.00 2013-12-02 16:12
'Function returns "True" if successful, otherwise "False"
'Amended from Source:
' http://gallery.technet.microsoft.com/scriptcenter/3879dd1b-09a1-4a9f-95ca-529351a7e2ac

Dim bTest, wndw
bTest = False
With CreateObject("shell.application")
    For Each wndw In .Windows
        If wndw = "Windows Explorer" Then
        If wndw.Document.Folder = sCurrentFolderName Then
            On Error Resume Next
            wndw.Quit
            bTest = Err.Number = 0
            On Error GoTo 0
        End If
        End If
    Next
End With ' shell.application
Z7_CloseExplorerWindow = CStr(bTest)

End Function