Delete text in incoming email - vba

I am trying to delete text in each incoming mail.
My rule settings are correct but my script is false.
Sub mails(MyMail As MailItem)
Dim newMail As MailItem
Set newMail = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Items.GetFirst
newMail.HTMLBody = Replace(newMail.HTMLBody, "Not Internal", "")
newMail.Save
End Sub

Try:
Sub mails(MyMail As MailItem)
With MyMail
If Instr(1, .HTMLBody, "Not Internal") > 0 Then
.HTMLBody = Replace(.HTMLBody, "Not Internal", "")
.Save
End If
End With
End Sub
Your original code created newMail as a copy of the first item in the default inbox and amended that email. My version processes the email passed to it by the rule. Note that the email is only amended and saved if the body includes the string "Not Internal".

Converting part of an email to a VBA assignment statement: Part 1
First the warnings:
Most of this code was written by me for me. The comments are so I can understand the code when I need to amend it 12 or 24 months after I wrote it. I have only added a few comments to help you. Try to understand what my code does but ask questions if necessary.
This system is work in progress. It is fairly typical of my developments when I do not fully understand the scope of what I am attempting. I create something simple using existing code and gradually improve it as my understanding of my requirement improves. Repeatedly updating code eventually means it is too messy to be updated again. I then redesign and rewrite ready for the next cycle of development. I do not know of any errors in this code but there will be scenarios I have never tested. Let me know of any problems. If necessary, use the email address in my profile to send me full details of a problem.
Having completed this answer, I can see that there is a lot for you to understand. Although macros do all the difficult stuff, understanding what they are doing and why will not be easy. Work through this answer slowly making sure you understand each step before moving onto the next. Good luck.
The first step is to discover what one of these emails look like to a VBA macro. This is the routine I use:
Option Explicit
Public Sub InvestigateEmailsFile()
' Outputs properties of selected emails to file "InvestigateEmails.txt"
' on the desktop.
' ??????? No record of when originally coded
' 22Oct16 Create separate version with output to file rather than
' Immediate Window.
' 15Jan19 Previously, control characters were represented by {cr}, {lf}
' and {tb}. There were replaced by ‹cr›, ‹lf› and ‹tb› on the
' assumption that these special characters would never appear
' in an email. "‹" is \u2039 and "›" is \u203A
' 4Feb19 Previous version had tidied text itself because OutLongTextRtn
' did not tidy text. Amended OutLongTextRtn to use TidyTextForDspl
' Technique for locating desktop from answer by Kyle:
' http://stackoverflow.com/a/17551579/973283
' Needs reference to "Microsoft Scripting Runtime"
Dim Exp As Explorer
Dim FileBody As String
Dim fso As FileSystemObject
Dim InxA As Long
Dim ItemCrnt As MailItem
Dim Path As String
Path = CreateObject("WScript.Shell").SpecialFolders("Desktop")
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Call MsgBox("Please select one or more emails then try again", vbOKOnly)
Exit Sub
Else
FileBody = ""
For Each ItemCrnt In Exp.Selection
If FileBody <> "" Then
FileBody = FileBody & vbLf
End If
With ItemCrnt
FileBody = FileBody & "From (Sender): " & .Sender
FileBody = FileBody & vbLf & "From (Sender name): " & .SenderName
FileBody = FileBody & vbLf & "From (Sender email address): " & _
.SenderEmailAddress
FileBody = FileBody & vbLf & "Subject: " & CStr(.Subject)
FileBody = FileBody & vbLf & "Received: " & Format(.ReceivedTime, "dmmmyy hh:mm:ss")
If .Attachments.Count = 0 Then
FileBody = FileBody & vbLf & "No attachments"
Else
FileBody = FileBody & vbLf & "Attachments:"
FileBody = FileBody & vbLf & "No.|Type|Path|Filename|DisplayName|"
For InxA = 1 To .Attachments.Count
With .Attachments(InxA)
FileBody = FileBody & vbLf & InxA & "|"
Select Case .Type
Case olByValue
FileBody = FileBody & "Val"
Case olEmbeddeditem
FileBody = FileBody & "Ebd"
Case olByReference
FileBody = FileBody & "Ref"
Case olOLE
FileBody = FileBody & "OLE"
Case Else
FileBody = FileBody & "Unk"
End Select
' Not all types have all properties. This code handles
' those missing properties of which I am aware. However,
' I have never found an attachment of type Reference or OLE.
' Additional code may be required for them.
Select Case .Type
Case olEmbeddeditem
FileBody = FileBody & "|"
Case Else
FileBody = FileBody & "|" & .Pathname
End Select
FileBody = FileBody & "|" & .Filename
FileBody = FileBody & "|" & .DisplayName & "|"
End With
Next
End If ' .Attachments.Count = 0
Call OutLongTextRtn(FileBody, "Text: ", .Body)
Call OutLongTextRtn(FileBody, "Html: ", .HtmlBody)
FileBody = FileBody & vbLf & "--------------------------"
End With
Next
End If
Call PutTextFileUtf8NoBom(Path & "\InvestigateEmails.txt", FileBody)
End Sub
Public Sub OutLongTextRtn(ByRef TextOut As String, ByVal Head As String, _
ByVal TextIn As String)
' * Break TextIn into lines of not more than 100 characters
' and append to TextOut.
' * The output is arranged so:
' xxxx|sssssssssssssss|
' |sssssssssssssss|
' |ssssssssss|
' where "xxxx" is the value of Head and "ssss..." are characters from
' TextIn. The third line in the example could be shorter because:
' * it contains the last few characters of TextIn
' * there a linefeed in TextIn
' * a <xxx> string recording whitespace would have been split
' across two lines.
‘ ??????? Date originally coded not recorded.
' 15Jan19 Added "|" at start and end of lines to make it clearer if
' whitespace added by this routine or in original TextIn
' 3Feb19 Discovered I had two versions of OutLongText. Renamed this version to
' indicate it returned a formatted string.
' 4Feb19 Previous version relied on the caller tidying text for display. This
' version expects TextIn to be untidied and uses TidyTextForDspl to tidy
' the text and then creates TextOut from its output.
If TextIn = "" Then
' Nothing to do
Exit Sub
End If
Const LenLineMax As Long = 100
'Dim LenLineCrnt As Long
Dim PosBrktEnd As Long ' Last > before PosEnd
Dim PosBrktStart As Long ' Last < before PosEnd
Dim PosNext As Long ' Start of block to be output after current block
Dim PosStart As Long ' First character of TextIn not yet output
'Dim TextInPart As String
TextIn = TidyTextForDspl(TextIn)
TextIn = Replace(TextIn, "lf›", "lf›" & vbLf)
PosStart = 1
Do While True
PosNext = InStr(PosStart, TextIn, vbLf)
If PosNext = 0 Then
' No LF in [Remaining] TextIn
'Debug.Assert False
PosNext = Len(TextIn) + 1
End If
If PosNext - PosStart > LenLineMax Then
PosNext = PosStart + LenLineMax
End If
' Check for <xxx> being split across lines
PosBrktStart = InStrRev(TextIn, "‹", PosNext - 1)
PosBrktEnd = InStrRev(TextIn, "›", PosNext - 1)
If PosBrktStart < PosStart And PosBrktEnd < PosStart Then
' No <xxx> within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And PosBrktEnd > 0 And PosBrktEnd > PosBrktStart Then
' Last or only <xxx> totally within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And _
(PosBrktEnd = 0 Or (PosBrktEnd > 0 And PosBrktEnd < PosBrktStart)) Then
' Last or only <xxx> will be split across rows
'Debug.Assert False
PosNext = PosBrktStart
Else
' Are there other combinations?
Debug.Assert False
End If
'Debug.Assert Right$(Mid$(TextIn, PosStart, PosNext - PosStart), 1) <> "‹"
If TextOut <> "" Then
TextOut = TextOut & vbLf
End If
If PosStart = 1 Then
TextOut = TextOut & Head & "|"
Else
TextOut = TextOut & Space(Len(Head)) & "|"
End If
TextOut = TextOut & Mid$(TextIn, PosStart, PosNext - PosStart) & "|"
PosStart = PosNext
If Mid$(TextIn, PosStart, 1) = vbLf Then
PosStart = PosStart + 1
End If
If PosStart > Len(TextIn) Then
Exit Do
End If
Loop
End Sub
Public Sub PutTextFileUtf8NoBom(ByVal PathFileName As String, ByVal FileBody As String)
' Outputs FileBody as a text file named PathFileName using
' UTF-8 encoding without leading BOM
' Needs reference to "Microsoft ActiveX Data Objects n.n Library"
' Addition to original code says version 2.5. Tested with version 6.1.
' 1Nov16 Copied from http://stackoverflow.com/a/4461250/973283
' but replaced literals with parameters.
' 15Aug17 Discovered routine was adding an LF to the end of the file.
' Added code to discard that LF.
' 11Oct17 Posted to StackOverflow
' 9Aug18 Comment from rellampec suggested removal of adWriteLine from
' WriteTest statement would avoid adding LF.
' 30Sep18 Amended routine to remove adWriteLine from WriteTest statement
' and code to remove LF from file. Successfully tested new version.
' References: http://stackoverflow.com/a/4461250/973283
' https://www.w3schools.com/asp/ado_ref_stream.asp
Dim BinaryStream As Object
Dim UTFStream As Object
Set UTFStream = CreateObject("adodb.stream")
UTFStream.Type = adTypeText
UTFStream.Mode = adModeReadWrite
UTFStream.Charset = "UTF-8"
UTFStream.Open
UTFStream.WriteText FileBody
UTFStream.Position = 3 'skip BOM
Set BinaryStream = CreateObject("adodb.stream")
BinaryStream.Type = adTypeBinary
BinaryStream.Mode = adModeReadWrite
BinaryStream.Open
UTFStream.CopyTo BinaryStream
UTFStream.Flush
UTFStream.Close
Set UTFStream = Nothing
BinaryStream.SaveToFile PathFileName, adSaveCreateOverWrite
BinaryStream.Flush
BinaryStream.Close
Set BinaryStream = Nothing
End Sub
Public Function TidyTextForDspl(ByVal Text As String) As String
' Tidy Text for dsplay by replacing white space with visible strings:
' Leave single space unchanged
' Replace single LF by ‹lf›
' Replace single CR by ‹cr›
' Replace single TB by ‹tb›
' Replace single non-break space by ‹nbs›
' Replace single CRLF by ‹crlf›
' Replace multiple spaces by ‹n s› where n is number of repeats
' Replace multiple LFs by ‹n lf› of white space character
' Replace multiple CRs by ‹cr› or ‹n cr›
' Replace multiple TBs by ‹n tb›
' Replace multiple non-break spaces by ‹n nbs›
' Replace multiple CRLFs by ‹n crlf›
' 15Mar16 Coded
' 3Feb19 Replaced "{" (\x7B) and "}" (\x7D) by "‹" (\u2039) and "›" (\u203A)
' on the grounds that the angle quotation marks were not likely to
' appear in text to be displayed.
' 5Feb19 Add code to treat CRLF as unit
Dim InsStr As String
Dim InxWsChar As Long
Dim NumWsChar As Long
Dim PosWsChar As Long
Dim RetnVal As String
Dim WsCharCrnt As Variant
Dim WsCharValue As Variant
Dim WsCharDspl As Variant
WsCharValue = VBA.Array(" ", vbCr & vbLf, vbLf, vbCr, vbTab, Chr(160))
WsCharDspl = VBA.Array("s", "crlf", "lf", "cr", "tb", "nbs")
RetnVal = Text
' Replace each whitespace individually
For InxWsChar = 0 To UBound(WsCharValue)
RetnVal = Replace(RetnVal, WsCharValue(InxWsChar), "‹" & WsCharDspl(InxWsChar) & "›")
Next
' Look for repeats. If found replace <x> by <n x>
For InxWsChar = 0 To UBound(WsCharValue)
PosWsChar = 1
Do While True
InsStr = "‹" & WsCharDspl(InxWsChar) & "›"
PosWsChar = InStr(PosWsChar, RetnVal, InsStr & InsStr)
If PosWsChar = 0 Then
' No [more] repeats of this <x>
Exit Do
End If
' Have <x><x>. Count number of extra <x>x
NumWsChar = 2
Do While Mid(RetnVal, PosWsChar + NumWsChar * Len(InsStr), Len(InsStr)) = InsStr
NumWsChar = NumWsChar + 1
Loop
RetnVal = Mid(RetnVal, 1, PosWsChar - 1) & _
"‹" & NumWsChar & " " & WsCharDspl(InxWsChar) & "›" & _
Mid(RetnVal, PosWsChar + NumWsChar * Len(InsStr))
PosWsChar = PosWsChar + Len(InsStr) * (1 - NumWsChar) + 1 + Len(NumWsChar)
Loop
Next
' Restore any single spaces
RetnVal = Replace(RetnVal, "‹" & WsCharDspl(0) & "›", " ")
TidyTextForDspl = RetnVal
End Function
The above code needs references to "Microsoft Scripting Runtime" and "Microsoft ActiveX Data Objects n.n Library".
For one of my emails, the above code creates a file on my desktop named “InvestigateEmails.txt”:
From (Sender): Zopa
From (Sender name): Zopa
From (Sender email address): zopa#mail.zopa.com
Subject: Jane, your weekly Zopa update
Received: 1Mar19 16:30:49
No attachments
Text: |The latest news from Zopa‹crlf›|
| <http://click.mail.zopa.com/?qs=df1dd45fb22f0a80e44887f2afb89fa999010ffe37c4dffba1b431d565441dc586e|
|95525d2f44408471d2d3f3d36fcf89cca0b23e2b9ff84> ‹tb› ‹crlf›|
|Can't see images?‹2 s›View in browser <http://view.mail.zopa.com/?qs=4fd1698978f7849d57bb369504b2222|
|ec6a4dab29397ae38367d7cb6cda466891c948bfdca1b6e9a91fdf2f03d994985087240cc3ba05080cb96697ecdafef5faae|
|24843efc1e3649f6b94139653b26d> ‹crlf›|
: : : :
|change your Contact Preferences.‹crlf›|
| <http://click.mail.zopa.com/open.aspx?ffcb10-fefa1375756d04-fe53157770600d7a7113-fe3e15707564047b71|
|1773-ff62107470-fe671673766d017d7516-ff9a1574> |
Html: |<!doctype html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xm|
|lns:o="urn:schemas-microsoft-com:office:office"><head> <title>Zopa</title> <!--[if !mso]><!-- --> <m|
|eta http-equiv="X-UA-Compatible" content="IE=edge"> <!--<![endif]-->‹2 s›<meta name="viewport" conte|
|nt="width=device-width,initial-scale=1"> ‹crlf›|
|<style type="text/css"> #outlook a { padding: 0; } .ReadMsgBody { width: 100%; } .ExternalClass { wi|
|dth: 100%; } .ExternalClass * { line-height: 100%; } body { margin: 0; padding: 0; -webkit-text-size|
: : : :
As you can see, this file lists the most interesting properties including the text and Html bodies. I add extra properties if I need to see them. The text and Html bodies are exactly as held by Outlook except I have replaced control characters with strings such has “‹crlf›”. This allows me to understand exactly what a VBA program will see if it is processing an email body.
Near the end of this email is a block of text the sender includes in all their emails. This is sort of block I assume you wish to remove from your emails.
Copy the above code to an Outlook module. Select one of the emails you wish to process and run macro “InvestigateEmailsFile()”. You should have a file on your desktop named “Explorer.txt”. Open that file with your favourite text editor and you should see something like the content above.

Converting part of an email to a VBA assignment statement: Part 2
At the end of Part 1, you should have a file on your desktop containing the Html body of one of the emails you wish to amend.
The next step is to create an XLSM workbook with one worksheet named “Body”. Expand columns “A” and “B” so “C” is just visible. Make column “A” a little wider than “B”. I find it helpful to format the worksheet as font Courier New” and size 9. Don’t worry too much about the size of the columns, you can adjust them later.
You now need to create a module within the workbook and copy this code to it:
Option Explicit
Sub ConvertBodyFromExplorerToVBA()
' Column A of worksheet "Body" contains all or part of the
' body of an email as output to file "Explorer.txt".
' On exit, the data in column A has been converted to
' VBA format in column B.
' 17Jan19 Coded as part of FormatBodyAsVBA V01.xlsm
' 10Mar19 Adjusted for the new format of "Explorer.txt"
' Added code to handle output that requires more
' continuation lines than allowed for VBA
Const MaxContLines As Long = 24 ' Maximum number of continuation lines per VBA statement
Const MaxLineLen As Long = 70 ' Normal maximum length of a line of the VBA string expression
Const MinPartLitLen As Long = 5 ' If a literal is split over two lines, neither part may be
' less than MinPartStrLen characters.
Dim BodyIn As String ' The string to be converted to a VBA string expression
Dim BodyPartsOut As New Collection ' Each element is a part of the VBA string expression
' Parts are "xxx" or vbCr or VbLf or so on
Dim CtrlCharType As String ' s, cr, lf, crlf or nbs
Dim CtrlCharVba As String ' VBA equivalent of s, cr, lf, crlf or nbs
Dim InxB As Long ' Inxex into BodyPartsOut
'Dim LenNextPart As Long
Dim LenOver As Long ' If a literal is to be split over two lines,
' the length for the next line
Dim LenThisLine As Long ' If a literal is to be split over two lines,
' the length for the current line
Dim LineCrnt As String ' Line imported from column A or
' line being built ready to be added to column B
Dim LenMax As Long ' Maximum length of string that can be added to LineCrnt
Dim NumContLines ' Number of contuation lines for current string expression
Dim NumRpts As Long ' # from ‹# xx›
Dim NumVariables As Long ' Number of variables required to hold output string expression
Dim PosInCrnt As Long ' Everything before position PosInCrnt of BodyIn
' has been output to BodyPartsOut
Dim PosInNext As Long ' Start of next control character or end of BodyIn
Dim PosV As Long ' Position of vertical bar within LineCrnt
Dim RowInCrnt As Long ' \ Used to control building of
Dim RowInLast As Long ' / BodyIn from input lines
Dim RowOutCrnt As Long ' Row of column B for LineCrnt
Dim UnitCrnt As String ' Holds a string literal while it is being split
' over multiple lines.
With Worksheets("Body")
.Columns(2).Clear
' The source within the text file will be of the form:
' Text: |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
' |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
' |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
' Html: |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
' |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
' |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
'
' Part of either a text body or an html body will have been copied to
' column 1 of worksheet "Body". Do not include any part of "Text:" or
' "Html:" as this will confuse the code that removes the start and end
' of each line.
' This For loop removes the leading " |" and trailing "|" from each
' line and joins the text between the vertical lines into a single string.
BodyIn = ""
RowInLast = .Cells(Rows.Count, "A").End(xlUp).Row
For RowInCrnt = 1 To RowInLast
LineCrnt = .Cells(RowInCrnt, "A").Value
If Right$(LineCrnt, 1) = "|" Then
' Remove trailing "|"
LineCrnt = Mid$(LineCrnt, 1, Len(LineCrnt) - 1)
End If
Do While Left$(LineCrnt, 1) = " "
' Remove leading space
LineCrnt = Mid$(LineCrnt, 2)
Loop
If Left$(LineCrnt, 1) = "|" Then
' Remove leading "|"
LineCrnt = Mid$(LineCrnt, 2)
End If
BodyIn = BodyIn & LineCrnt
Next
End With
' Display BodyIn as a diagnostic aid.
Debug.Print "[" & Replace(BodyIn, "lf›", "lf›" & vbLf) & "]"
'Debug.Assert False
' * This Do loop converts BodyIn into the units of a VBA string expression
' and stores them in collection BodyPartsOut. These units are "xxxx",
' vbCr, vbLf, vbCrLf, vbTab, Chr$(160) and String(#, "x").
' * The input is ... xxxxxx‹# yy›xxxxxx‹yy›xxxxxx‹# yy› ...
' * This loop puts speech marks around each string of xs to create a string
' literal and decodes each ‹...› and creates one or more of the other
' units as appropriate.
PosInCrnt = 1
Do While PosInCrnt <= Len(BodyIn)
'Find next control character if any
PosInNext = InStr(PosInCrnt, BodyIn, "‹")
If PosInNext = 0 Then
' No [more] control characters in BodyIn.
'Debug.Assert False
PosInNext = Len(BodyIn) + 1
End If
If PosInCrnt = PosInNext Then
' Next character of BodyIn is the start of control character
PosInCrnt = PosInCrnt + 1
If IsNumeric(Mid$(BodyIn, PosInCrnt, 1)) Then
' Control string is of the form: ‹# xx› where
' # is the number of repeats of control character xx
PosInNext = InStr(PosInCrnt, BodyIn, " ")
NumRpts = Mid$(BodyIn, PosInCrnt, PosInNext - PosInCrnt)
PosInCrnt = PosInNext + 1
Else
' Control string is of the form: ‹xx› where xx identifies a control character
NumRpts = 1
PosInCrnt = PosInNext + 1
End If
PosInNext = InStr(PosInCrnt, BodyIn, "›")
CtrlCharType = Mid$(BodyIn, PosInCrnt, PosInNext - PosInCrnt)
PosInCrnt = PosInNext + 1
Select Case CtrlCharType
Case "s"
' CtrlCharVba not used for space
Case "crlf"
CtrlCharVba = "vbCrLf"
Case "tb"
CtrlCharVba = "vbTab"
Case "cr"
CtrlCharVba = "vbCr"
Case "lf"
CtrlCharVba = "vbLf"
Case "nbs"
CtrlCharVba = "Chr$(160)"
Case Else
Debug.Assert False ' Error. Unknown control character type
End Select
If NumRpts = 1 Then
' Note: no single spaces
BodyPartsOut.Add CtrlCharVba
ElseIf CtrlCharType = "s" Then
' Single, repeating space
BodyPartsOut.Add "Space(" & NumRpts & ")"
ElseIf CtrlCharType <> "crlf" Then
' Single, repeating control character
BodyPartsOut.Add "String(" & NumRpts & ", " & CtrlCharVba & ")"
Else
' Double, repeating control character
Do While NumRpts > 0
BodyPartsOut.Add CtrlCharVba
NumRpts = NumRpts - 1
Loop
End If
Else
' Convert display characters PosInCrnt to PosInNext of BodyIn to a string literal
BodyPartsOut.Add """" & Mid$(BodyIn, PosInCrnt, PosInNext - PosInCrnt) & """"
PosInCrnt = PosInNext
End If
Loop
' Display the elements of BodyPartsOut as a diagnostic aid.
Debug.Print
Debug.Print "[";
LineCrnt = ""
For InxB = 1 To BodyPartsOut.Count
If InxB > 1 Then
LineCrnt = LineCrnt & " & "
End If
If Len(LineCrnt) + 3 + Len(BodyPartsOut(InxB)) > MaxLineLen Then
Debug.Print LineCrnt & " _"
LineCrnt = ""
End If
LineCrnt = LineCrnt & BodyPartsOut(InxB)
Next
Debug.Print LineCrnt & "]"
'Debug.Assert False
Debug.Print
RowOutCrnt = 1
NumVariables = 1
NumContLines = 0
LineCrnt = " Text1 = "
With Worksheets("Body")
' This For loop converts the seperate units in BodyPartsOut into a string
' expression by places " & " between each unit and outputting the result
' to column B of worksheet "Body". It also cuts the entire string
' expression into lines of about MaxLineLen characters and adds " _" at
' the end of each line except the last.
For InxB = 1 To BodyPartsOut.Count
If InxB > 1 Then
' " & " needed before every unit except the first
LineCrnt = LineCrnt & " & "
End If
' The IIf below returns 2 (the length of " _") except for the last unit
' for which it returns 0. This allows for a line continuation if necessary.
If Len(LineCrnt) + IIf(InxB = BodyPartsOut.Count, 0, 4) + _
Len(BodyPartsOut(InxB)) <= MaxLineLen Then
' Can fit the whole of the next body part onto the next line
'Debug.Assert False
LineCrnt = LineCrnt & BodyPartsOut(InxB)
'Debug.Print "LineCrnt [" & LineCrnt & "]"
ElseIf Left$(BodyPartsOut(InxB), 1) <> """" Then
' Unit is not a literal so cannot be split. Place on following line
'Debug.Assert False
If NumContLines = MaxContLines Then
'Debug.Assert False
LineCrnt = Mid$(LineCrnt, 1, Len(LineCrnt) - 2) ' Remove concatenation
.Cells(RowOutCrnt, "B").Value = LineCrnt
' Start new variable
NumVariables = NumVariables + 1
NumContLines = 0
LineCrnt = " Text" & NumVariables & " = "
Else
'Debug.Assert False
.Cells(RowOutCrnt, "B").Value = LineCrnt & "_"
NumContLines = NumContLines + 1
LineCrnt = Space(10)
End If
Debug.Print "Row " & PadL(RowOutCrnt, 2) & " [" & .Cells(RowOutCrnt, "B").Value & "]"
RowOutCrnt = RowOutCrnt + 1
LineCrnt = LineCrnt & BodyPartsOut(InxB)
'Debug.Print "LineCrnt [" & LineCrnt & "]"
Else
'Debug.Assert False
' Unit is a literal which can be split over two or more lines
' A collection element cannot be amended so copy to variable
' without speech marks.
UnitCrnt = Mid$(BodyPartsOut(InxB), 2, Len(BodyPartsOut(InxB)) - 2)
Do While UnitCrnt <> ""
'Debug.Assert False
LenThisLine = MaxLineLen - Len(LineCrnt) - 4 ' 4 for " & _"
LenOver = Len(UnitCrnt) - LenThisLine
If LenOver < 0 Then
LenOver = 0
End If
If LenOver = 0 Then
' Can fit remainder of UnitCrnt on current line
'Debug.Assert False
' Double any speech marks within literal
LineCrnt = LineCrnt & """" & Replace(UnitCrnt, """", """""") & """"
'Debug.Print "LineCrnt [" & LineCrnt & "]"
Exit Do
ElseIf LenThisLine < MinPartLitLen Then
' No room for part of literal on current line so settle for short line
Debug.Assert False
If NumContLines = MaxContLines Then
Debug.Assert False
LineCrnt = Mid$(LineCrnt, 1, Len(LineCrnt) - 2) ' Remove concatenation
.Cells(RowOutCrnt, "B").Value = LineCrnt
' Start new variable
NumVariables = NumVariables + 1
NumContLines = 0
LineCrnt = " Text" & NumVariables & " = "
Else
Debug.Assert False
.Cells(RowOutCrnt, "B").Value = LineCrnt & "_"
NumContLines = NumContLines + 1
LineCrnt = Space(10)
End If
Debug.Print "Row " & PadL(RowOutCrnt, 2) & " [" & .Cells(RowOutCrnt, "B").Value & "]"
RowOutCrnt = RowOutCrnt + 1
LineCrnt = LineCrnt & BodyPartsOut(InxB)
' Loop to fit all or part of UnitCrnt onto next line
ElseIf LenOver < MinPartLitLen Then
' Left over portion of literal too short to be split off.
' Settle for overlength current line
Debug.Assert False
LineCrnt = LineCrnt & """" & Replace(UnitCrnt, """", """""") & """ &"
If NumContLines = MaxContLines Then
Debug.Assert False
LineCrnt = Mid$(LineCrnt, 1, Len(LineCrnt) - 2) ' Remove concatenation
.Cells(RowOutCrnt, "B").Value = LineCrnt
' Start new variable
NumVariables = NumVariables + 1
NumContLines = 0
LineCrnt = " Text" & NumVariables & " = "
Else
Debug.Assert False
.Cells(RowOutCrnt, "B").Value = LineCrnt & "_"
NumContLines = NumContLines + 1
LineCrnt = Space(10)
End If
Debug.Print "Row " & PadL(RowOutCrnt, 2) & " [" & .Cells(RowOutCrnt, "B").Value & "]"
RowOutCrnt = RowOutCrnt + 1
Else
' UnitCrnt can be split. Fit what can onto current line
'Debug.Assert False
LineCrnt = LineCrnt & """" & _
Replace(Left$(UnitCrnt, LenThisLine), """", """""") & """ & "
If NumContLines = MaxContLines Then
'Debug.Assert False
LineCrnt = Mid$(LineCrnt, 1, Len(LineCrnt) - 2) ' Remove concatenation
.Cells(RowOutCrnt, "B").Value = LineCrnt
' Start new variable
NumVariables = NumVariables + 1
NumContLines = 0
LineCrnt = " Text" & NumVariables & " = "
Else
'Debug.Assert False
.Cells(RowOutCrnt, "B").Value = LineCrnt & "_"
NumContLines = NumContLines + 1
LineCrnt = Space(10)
End If
Debug.Print "Row " & PadL(RowOutCrnt, 2) & " [" & .Cells(RowOutCrnt, "B").Value & "]"
UnitCrnt = Mid$(UnitCrnt, LenThisLine + 1)
RowOutCrnt = RowOutCrnt + 1
' Loop to fit all or part of UnitCrnt onto next line
End If ' List of alternative splitting techniques for handling overlength unit
Loop ' Until all of UnitCrnt has been output
End If ' UnitCrnt fits onto current line or list of alternative choices
Next InxB
If LineCrnt <> "" Then
.Cells(RowOutCrnt, "B").Value = LineCrnt
Debug.Print "Row " & RowOutCrnt & " [" & .Cells(RowOutCrnt, "B").Value & "]"
End If
End With
End Sub
Sub TestConvertOutput()
Dim Text1 As String
Dim Text2 As String
Dim TextToBeRemoved As String
TextToBeRemoved = Text1 & Text2
Debug.Print TidyTextForDspl(TextToBeRemoved)
End Sub
Public Function PadL(ByVal Str As String, ByVal PadLen As Long, _
Optional ByVal PadChr As String = " ") As String
' Pad Str with leading PadChr to give a total length of PadLen
' If the length of Str exceeds PadLen, Str will not be truncated
' Sep15 Coded
' 20Dec15 Added code so overlength strings are not truncated
' 10Jun16 Added PadChr so could pad with characters other than space
If Len(Str) >= PadLen Then
' Do not truncate over length strings
PadL = Str
Else
PadL = Right$(String(PadLen, PadChr) & Str, PadLen)
End If
End Function
The Outlook code includes macro TidyTextForDspl. You will need this macro in the Excel module as well.
I doubt if the Outlook code will give you any problems because I have been using that code for some time. My only concern is I have forgotten to include one of my library routines which are not in the same module as macro InvestigateEmailsFile. This Excel code is experimental. I have tested it on Html that I hope is more complicated than yours. That Html converted to a string expression that exceeded a VBA limit. This weekend I have extended to code to avoid that limit.
Now return to “Explorer.txt”. Select and copy the entire block you want to remove. (I will explain this below.) Switch to the workbook and paste into Cell A1 of worksheet “Body”. With my example email, column “A” looks like:
<div style="font-family:Verdana;font-size:12px;font-weight:400;line-height:16px;text-align:lef|
|t;color:#ABABAB;">‹crlf›|
|‹16 s›Zopa Limited is authorised and regulated by the Financial Conduct Authority, and entered on th|
|e Financial Services Register (<span style="color:#00B9A7;">718925</span>). Zopa Bank Limited is aut|
|horised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and |
|the Prudential Regulation Authority, and entered on the Financial Services Register (<span style="co|
|lor:#00B9A7;">800542</span>). Zopa Limited (<span style="color:#00B9A7;">05197592</span>) and Zopa B|
|ank Limited (<span style="color:#00B9A7;">10627575</span>) are both incorporated in England & Wa|
|les and have their registered office at: 1st Floor, Cottons Centre, Tooley Street, London, SE1 2QG.<|
|br>‹crlf›|
|‹16 s›<br>‹crlf›|
|‹16 s›© Zopa Bank Limited 2019 All rights reserved. 'Zopa' is a trademark of Zopa Bank Limited.|
|<br>‹crlf›|
|‹16 s›<br>‹crlf›|
|‹16 s›Zopa is a member of Cifas – the UK’s leading anti-fraud association, and we are re|
|gistered with the Office of the Information Commissioner (<span style="color:#00B9A7;">ZA275984</spa|
|n>, <span style="color:#00B9A7;">Z8797078</span>).<br>‹crlf›|
|‹16 s›<br>‹crlf›|
|‹16 s›No longer want to receive our emails? <a‹2 s›href="http://click.mail.zopa.com/?qs=df1dd45fb22f|
|0a804e99ede07e73c95c826908dfc9aef47f93c598c0c6537648c2c346408fab877afa32022afc1a846a3060560073066676|
|d72d0a4720039df6" style="color: #ffffff; font-weight: 700; text-decoration: none;">Unsubscribe</a> o|
|r sign into your <a‹2 s›href="http://click.mail.zopa.com/?qs=df1dd45fb22f0a80c21dc52c7c6968eb3af863f|
|9656119ff373444e56f12bbc5c50c416ecbcd8e2c0192ac31983d91b06478e0f60261102d" style="color: #ffffff; fo|
|nt-weight: 700; text-decoration: none;">Zopa Account</a> to change your Contact Preferences.</div>
I found this block by searching for “Html:” and then “Zopa Limited is authorised”. You need to search for the start of the text you want to remove. Next is the difficult step. You need to identify the entire block you want to remove.
If you look at my example, the block starts <div style="font and end </div>. You say the text you want to remove is coloured. Note, the style attribute for the <div> start tag ends color:#ABABAB. You will almost certainly have something similar at the start of the block you want to remove since this is what colours the text. You need to remove the entire block; not just the text but the Html envelope around that text. That envelope will probably be <div> to </div> but there are plenty of other possible envelopes. For a future version of my system, I plan to select the text and have a macro identify the start and end of the block containing that text. But with the current version, you have to identify the block.
As I have already said, you need to select the entire block and copy and paste it to column A of worksheet “Body”. Note, I have only selected the block so in my example above, the first and last lines of column A are short.
So "Explorer.Txt" contains properties, in a human readable format, of the email from you wish to delete a block of text. You have copied that block including its Html envelope to column A of worksheet "Body".
Run macro “ConvertBodyFromExplorerToVBA()”
I have left diagnostic code in this macro and Debug.Assert False statements so you can look at the diagnostic output to the Immediate Window. When you have finished looking at the output, click [F5]. When the macro is finished, column B should look like:
Text1 = "<div style=""font-family:Verdana;font-size:12px;font-weig" & _
"ht:400;line-height:16px;text-align:left;color:#ABABAB;"">" & _
vbCrLf & Space(16) & "Zopa Limited is authorised and regu" & _
"lated by the Financial Conduct Authority, and entered on" & _
" the Financial Services Register (<span style=""color:#00" & _
"B9A7;"">718925</span>). Zopa Bank Limited is authorised b" & _
"y the Prudential Regulation Authority and regulated by t" & _
"he Financial Conduct Authority and the Prudential Regula" & _
"tion Authority, and entered on the Financial Services Re" & _
"gister (<span style=""color:#00B9A7;"">800542</span>). Zop" & _
"a Limited (<span style=""color:#00B9A7;"">05197592</span>)" & _
" and Zopa Bank Limited (<span style=""color:#00B9A7;"">106" & _
"27575</span>) are both incorporated in England & Wal" & _
"es and have their registered office at: 1st Floor, Cotto" & _
"ns Centre, Tooley Street, London, SE1 2QG.<br>" & _
vbCrLf & Space(16) & "<br>" & vbCrLf & Space(16) & "&copy" & _
"; Zopa Bank Limited 2019 All rights reserved. 'Zopa' is " & _
"a trademark of Zopa Bank Limited.<br>" & vbCrLf & _
Space(16) & "<br>" & vbCrLf & Space(16) & "Zopa is a memb" & _
"er of Cifas – the UK’s leading anti-fraud as" & _
"sociation, and we are registered with the Office of the " & _
"Information Commissioner (<span style=""color:#00B9A7;"">Z" & _
"A275984</span>, <span style=""color:#00B9A7;"">Z8797078</s" & _
"pan>).<br>" & vbCrLf & Space(16) & "<br>" & vbCrLf & _
Space(16) & "No longer want to receive our emails? <a"
Text2 = Space(2) & "href=""http://click.mail.zopa.com/?qs=df1dd45f" & _
"b22f0a804e99ede07e73c95c826908dfc9aef47f93c598c0c6537648" & _
"c2c346408fab877afa32022afc1a846a3060560073066676d72d0a47" & _
"20039df6"" style=""color: #ffffff; font-weight: 700; text-" & _
"decoration: none;"">Unsubscribe</a> or sign into your <a" & _
Space(2) & "href=""http://click.mail.zopa.com/?qs=df1dd45f" & _
"b22f0a80c21dc52c7c6968eb3af863f9656119ff373444e56f12bbc5" & _
"c50c416ecbcd8e2c0192ac31983d91b06478e0f60261102d"" style=" & _
"""color: #ffffff; font-weight: 700; text-decoration: none" & _
";"">Zopa Account</a> to change your Contact Preferences.<" & _
"/div>"
My text block is so long, the output exceeded the VBA limit of 24 continuation lines so there are two assignment statements in Column B. You may only need one assignment statement or you may need more.
The macro has converted the text in column A to VBA assignment statements in column B ready to be copied to your macro.
To test the output, select all the text in column B. Switch to the VBA Editor and find macro TestConvertOutput. Paste the text from column B into the gap between Dim TextToBeRemoved As String and TextToBeRemoved = Text1 & Text2. There should be no syntax errors. If you don’t need Text2 or if you need Text3, amend the routine as necessary. If you run macro TestConvertOutput, it should output the block to be deleted to the Immediate Window with any errors.
The statements in macro TestConvertOutput are those you need for macro mails. TextToBeRemoved is the value to replace “Not Internal”.

Related

Removing a string that includes CRLF characters from body of e-mail

I am trying to remove a string from selected incoming MS Outlook (2016) e-mail.
The string is two sentences in German language. I use the Replace() function. This principally works. (See my full procedure below.)
The two sentences are sometimes separated by CRLF (Carriage Return, Line Feed) characters, and these are not always at the same place. This seems to be the result of these e-mails passing through various devices before they land in my Outlook inbox.
First address the simpler part of the problem
Before addressing the issue of the CRLF appearing on varying places, I want to create a procedure that deals with strings with CRLF at fixed positions.
How the source code of such a string would look:
(Screen shot history: I saved the e-mail as .html on my harddisk, then opened the .html file in Notepad++, to see the CRLF characters.)
The html tags are not that relevant for me. They can remain in the e-mail. (In fact, the formatting tags vary, too, so it is better to not start tackling them at all.) My only concern is to remove the visible part, i.e. the text "Diese E-Mail kommt... vertrauenswürdig halten".
I tried to catch text with line breaks by including the CR LF part as Chr():
strDelete01 = "Diese E-Mail kommt von Personen" & Chr(13) & Chr(10) & "außerhalb der Stadtverwaltung. Klicken Sie nur auf Links oder Dateianhnge," & Chr(13) & Chr(10) & "wenn Sie die Personenn für vertrauenswürdig halten."
My procedure does not recognize the string, and accordingly does nothing.
My script so far
Public Sub EditBodyCgReplace()
'Declarations
Dim obj As Object
Dim Sel As Outlook.Selection
Dim DoSave As Boolean
Dim NewBody As String
Dim strDelete01 As String
Dim strDelete02 As String
Dim strDelete03 As String
Dim strDelete04 As String
'Fill the variables
strDelete01 = "Diese E-Mail kommt von Personen außerhalb der Stadtverwaltung. Klicken Sie nur auf Links oder Dateianhänge, wenn Sie die Personen für vertrauenswürdig halten."
strDelete02 = "################################################################################"
strDelete03 = <hr>
strDelete04 = "Diese E-Mail kommt von Personen" & Chr(13) & Chr(10) & "außerhalb der Stadtverwaltung. Klicken Sie nur auf Links oder Dateianhnge," & Chr(13) & Chr(10) & "wenn Sie die Personenn für vertrauenswürdig halten."
'Note: I am playing here with various types of strings at once. For example,
'the procedure will also remove <hr> lines and "#####" strings
'Work with it
If TypeOf Application.ActiveWindow Is Outlook.Inspector Then
Set obj = Application.ActiveInspector.CurrentItem
Else
Set Sel = Application.ActiveExplorer.Selection
If Sel.Count Then
Set obj = Sel(1)
DoSave = True
End If
End If
If Not obj Is Nothing Then
NewBody = Replace(obj.HTMLBody, strDelete01, "")
NewBody = Replace(obj.HTMLBody, strDelete02, "")
NewBody = Replace(obj.HTMLBody, strDelete03, "")
NewBody = Replace(obj.HTMLBody, strDelete04, "")
If NewBody <> "" Then
obj.HTMLBody = NewBody
If DoSave Then
obj.Save
End If
End If
End If
End Sub
Question: What can I do to include the CRLF in the search string?
Follow-up question: What can I do to remove such strings with CRLF included in varying places? Is there a way to use regular expressions? Can VBA in Outlook deal with it? - Idea: if regular expressions work, perhaps the entire CRLF issue is not an issue anymore, as the expression would look something like
"Diese E-Mail kommt von * vertrauenswürdig halten."
and thus include anything - including CRLF - in the middle?
Perhaps important
After doing various experiments I am starting to feel that MS Outlook does not use HTML at all in its e-mails?
I observe I can practically not address any html code in the obj.HTMLBody. I can address plain text. I cannot address parts of html such as "<hr ", or at least that is what I believe to be observing. (There was a moment when I could address "<hr>" and thus delete it, but I cannot recreate the conditions where this worked yesterday.)
I can save the e-mails as html files (outside Outlook, somewhere on my harddisk in a separate folder), and in these files I do see the CRLF and other stuff. But perhaps the e-mails, as long as kept in Outlook itself, are stored using some other code?
So what is this code, and how can I address parts of it for deleting?
My full diagnostic routine
The subroutine InvestigateEmails() will output to either the Immediate Window or a file on the desktop. The Immediate Window is usually the more convenient but has a limit of about 200 lines. So if the output is likely to be over 200 lines, output must be to a file. If output is less than 200 lines, the choice is yours.
For output to the Immediate Window, review subroutine OutSomeProperties. Add any properties that you wish to see but are missing. Consider removing any properties not currently required. Check that #Const Selected = True.
For output to a file, review subroutine OutAllProperties. More correctly this should be “all properties of which I am aware and have ever been interested in.” You may wish to check that all the properties of interest to you are included. I recommend not removing any existing properties. Check that #Const Selected = False.
Select the emails whose properties you wish to see. Run subroutine InvestigateEmails()
This code uses conditional compiling which will be confusing to a programmer not familiar with this technique. Either research conditional compiling or accept that it is doing something useful that you do not need to understand.
Option Explicit
' This code requires references to:
' "Microsoft Scripting Runtime"
' "Microsoft ActiveX Data Objects n.n Library". Tested with version 6.1.
Public Sub InvestigateEmails()
' Outputs all or selected properties of one or more emails.
' ========================================================================
' "Selected = True" to output a small number of properties for
' a small number of emails to the Immediate Window.
' "Selected = False" to output all properties for any number of emails
' to desktop file "InvestigateEmails.txt".
#Const Selected = True
' ========================================================================
' Technique for locating desktop from answer by Kyle:
' http://stackoverflow.com/a/17551579/973283
Dim Exp As Explorer
Dim ItemCrnt As MailItem
#If Not Selected Then
Dim FileBody As String
Dim Fso As FileSystemObject
Dim Path As String
Path = CreateObject("WScript.Shell").specialfolders("Desktop")
#End If
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Call MsgBox("Please select one or more emails then try again", vbOKOnly)
Exit Sub
Else
For Each ItemCrnt In Exp.Selection
If ItemCrnt.Class = olMail Then
#If Selected Then
Call OutSomeProperties(ItemCrnt)
#Else
Call OutAllProperties(ItemCrnt, FileBody)
#End If
End If
Next
End If
#If Not Selected Then
Call PutTextFileUtf8NoBom(Path & "\InvestigateEmails.txt", FileBody)
#End If
End Sub
Public Sub OutSomeProperties(ItemCrnt As Outlook.MailItem)
' Outputs selected properties of a MailItem to the Immediate Window.
' The Immediate Window can only display about 200 rows before the older
' rows start scrolling off the top. This means this routine is only
' suitable for displaying a small number of simple properties. Add or
' remove properties as necessary to meet the current requirement.
Dim InxA As Long
Dim InxR As Long
Debug.Print "=============================================="
With ItemCrnt
Debug.Print " EntryId: " & .EntryID
Debug.Print " Created: " & .CreationTime
Debug.Print " Receiver: " & .ReceivedByName
Debug.Print " Received: " & .ReceivedTime
For InxR = 1 To .Recipients.Count
Debug.Print "Recipient: " & .Recipients(InxR)
Next
Debug.Print " Sender: " & .Sender
Debug.Print " SenderEA: " & .SenderEmailAddress
Debug.Print " SenderNm: " & .SenderName
Debug.Print " SentOn: " & .SentOn
Debug.Print " Subject: " & .Subject
Debug.Print " To: " & .To
If .Attachments.Count > 0 Then
Debug.Print "Attachments:"
For InxA = 1 To .Attachments.Count
Debug.Print " " & InxA & ": " & .Attachments(InxA).DisplayName
Next
End If
End With
End Sub
Sub OutAllProperties(ItemCrnt As Outlook.MailItem, ByRef FileBody As String)
' Adds all properties of a MailItem to FileBody.
' The phrase "all properties" should more correctly be "all properties
' that I know of and have ever been interested in".
' Source of PropertyAccessor information:
' https://www.slipstick.com/developer/read-mapi-properties-exposed-outlooks-object-model/
Dim InxA As Long
Dim InxR As Long
Dim PropAccess As Outlook.propertyAccessor
If FileBody <> "" Then
FileBody = FileBody & String(80, "=") & vbLf
End If
With ItemCrnt
FileBody = FileBody & "EntryId: " & .EntryID
FileBody = FileBody & "From (Sender): " & .Sender
FileBody = FileBody & vbLf & "From (Sender name): " & .SenderName
FileBody = FileBody & vbLf & "From (Sender email address): " & _
.SenderEmailAddress
FileBody = FileBody & vbLf & "Subject: " & CStr(.Subject)
FileBody = FileBody & vbLf & "Received: " & Format(.ReceivedTime, "dmmmyy hh:mm:ss")
FileBody = FileBody & vbLf & "To: " & .To
FileBody = FileBody & vbLf & "CC: " & .CC
FileBody = FileBody & vbLf & "BCC: " & .BCC
If .Attachments.Count = 0 Then
FileBody = FileBody & vbLf & "No attachments"
Else
FileBody = FileBody & vbLf & "Attachments:"
FileBody = FileBody & vbLf & "No.|Type|Path|Filename|DisplayName|"
For InxR = 1 To .Recipients.Count
FileBody = FileBody & vbLf & "Recipient" & InxR & ": " & .Recipients(InxR)
Next
For InxA = 1 To .Attachments.Count
With .Attachments(InxA)
FileBody = FileBody & vbLf & InxA & "|"
Select Case .Type
Case olByValue
FileBody = FileBody & "Val"
Case olEmbeddeditem
FileBody = FileBody & "Ebd"
Case olByReference
FileBody = FileBody & "Ref"
Case olOLE
FileBody = FileBody & "OLE"
Case Else
FileBody = FileBody & "Unk"
End Select
' Not all types have all properties. This code handles
' those missing properties of which I am aware. However,
' I have never found an attachment of type Reference or OLE.
' Additional code may be required for them.
Select Case .Type
Case olEmbeddeditem
FileBody = FileBody & "|"
Case Else
FileBody = FileBody & "|" & .Pathname
End Select
FileBody = FileBody & "|" & .FileName
FileBody = FileBody & "|" & .DisplayName & "|"
End With
Next
End If ' .Attachments.Count = 0
Call OutLongTextRtn(FileBody, "Text: ", .Body)
Call OutLongTextRtn(FileBody, "Html: ", .HtmlBody)
Set PropAccess = .propertyAccessor
FileBody = FileBody & vbLf & "PR_RECEIVED_BY_NAME: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0040001E")
FileBody = FileBody & vbLf & "PR_SENT_REPRESENTING_NAME: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0042001E")
FileBody = FileBody & vbLf & "PR_REPLY_RECIPIENT_NAMES: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0050001E")
FileBody = FileBody & vbLf & "PR_SENT_REPRESENTING_EMAIL_ADDRESS: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0065001E")
FileBody = FileBody & vbLf & "PR_RECEIVED_BY_EMAIL_ADDRESS: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0076001E")
FileBody = FileBody & vbLf & "PR_TRANSPORT_MESSAGE_HEADERS:" & vbLf & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E")
FileBody = FileBody & vbLf & "PR_SENDER_NAME: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0C1A001E")
FileBody = FileBody & vbLf & "PR_SENDER_EMAIL_ADDRESS: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0C1F001E")
FileBody = FileBody & vbLf & "PR_DISPLAY_BCC: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0E02001E")
FileBody = FileBody & vbLf & "PR_DISPLAY_CC: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0E03001E")
FileBody = FileBody & vbLf & "PR_DISPLAY_TO: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0E04001E")
FileBody = FileBody & vbLf
Set PropAccess = Nothing
End With
End Sub
Sub OutLongTextRtn(ByRef TextOut As String, ByVal Head As String, _
ByVal TextIn As String)
' * Break TextIn into lines of not more than 100 characters
' and append to TextOut.
' * The output is arranged so:
' xxxx|sssssssssssssss|
' |sssssssssssssss|
' |ssssssssss|
' where "xxxx" is the value of Head and "ssss..." are characters from
' TextIn. The third line in the example could be shorter because:
' * it contains the last few characters of TextIn
' * there a linefeed in TextIn
' * a <xxx> string recording whitespace would have been split
' across two lines.
If TextIn = "" Then
' Nothing to do
Exit Sub
End If
Const LenLineMax As Long = 100
Dim PosBrktEnd As Long ' Last > before PosEnd
Dim PosBrktStart As Long ' Last < before PosEnd
Dim PosNext As Long ' Start of block to be output after current block
Dim PosStart As Long ' First character of TextIn not yet output
TextIn = TidyTextForDspl(TextIn)
TextIn = Replace(TextIn, "lf›", "lf›" & vbLf)
PosStart = 1
Do While True
PosNext = InStr(PosStart, TextIn, vbLf)
If PosNext = 0 Then
' No LF in [Remaining] TextIn
'Debug.Assert False
PosNext = Len(TextIn) + 1
End If
If PosNext - PosStart > LenLineMax Then
PosNext = PosStart + LenLineMax
End If
' Check for <xxx> being split across lines
PosBrktStart = InStrRev(TextIn, "‹", PosNext - 1)
PosBrktEnd = InStrRev(TextIn, "›", PosNext - 1)
If PosBrktStart < PosStart And PosBrktEnd < PosStart Then
' No <xxx> within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And PosBrktEnd > 0 And PosBrktEnd > PosBrktStart Then
' Last or only <xxx> totally within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And _
(PosBrktEnd = 0 Or (PosBrktEnd > 0 And PosBrktEnd < PosBrktStart)) Then
' Last or only <xxx> will be split across rows
'Debug.Assert False
PosNext = PosBrktStart
Else
' Are there other combinations?
Debug.Assert False
End If
'Debug.Assert Right$(Mid$(TextIn, PosStart, PosNext - PosStart), 1) <> "‹"
If TextOut <> "" Then
TextOut = TextOut & vbLf
End If
If PosStart = 1 Then
TextOut = TextOut & Head & "|"
Else
TextOut = TextOut & Space(Len(Head)) & "|"
End If
TextOut = TextOut & Mid$(TextIn, PosStart, PosNext - PosStart) & "|"
PosStart = PosNext
If Mid$(TextIn, PosStart, 1) = vbLf Then
PosStart = PosStart + 1
End If
If PosStart > Len(TextIn) Then
Exit Do
End If
Loop
End Sub
Sub PutTextFileUtf8NoBom(ByVal PathFileName As String, ByVal FileBody As String)
' Outputs FileBody as a text file named PathFileName using
' UTF-8 encoding without leading BOM
' 1Nov16 Copied from http://stackoverflow.com/a/4461250/973283
' but replaced literals with parameters.
' 15Aug17 Discovered routine was adding an LF to the end of the file.
' Added code to discard that LF.
' 11Oct17 Posted to StackOverflow
' 9Aug18 Comment from rellampec suggested removal of adWriteLine from
' WriteTest statement would avoid adding LF.
' 30Sep18 Amended routine to remove adWriteLine from WriteTest statement
' and code to remove LF from file. Successfully tested new version.
' References: http://stackoverflow.com/a/4461250/973283
' https://www.w3schools.com/asp/ado_ref_stream.asp
Dim BinaryStream As Object
Dim UTFStream As Object
Set UTFStream = CreateObject("adodb.stream")
UTFStream.Type = adTypeText
UTFStream.Mode = adModeReadWrite
UTFStream.Charset = "UTF-8"
UTFStream.Open
UTFStream.WriteText FileBody
UTFStream.Position = 3 'skip BOM
Set BinaryStream = CreateObject("adodb.stream")
BinaryStream.Type = adTypeBinary
BinaryStream.Mode = adModeReadWrite
BinaryStream.Open
UTFStream.CopyTo BinaryStream
UTFStream.Flush
UTFStream.Close
Set UTFStream = Nothing
BinaryStream.SaveToFile PathFileName, adSaveCreateOverWrite
BinaryStream.Flush
BinaryStream.Close
Set BinaryStream = Nothing
End Sub
I will answer your question in parts as I have the spare time. Someone else may get to the important bit before I do.
I have edited your question. I did not understand a couple of sentences so I looked at the source and found my suspicion was correct, you had included less than characters. Stack Overflow permits a limited number of Html tags. Anything else that looks like an Html tag is ignored. I replaced each "<" with "<" so readers could see your Html. I can add an explanation if you do not understand why this works.
You have:
NewBody = Replace(obj.HTMLBody, strDelete01, "")
NewBody = Replace(obj.HTMLBody, strDelete02, "")
NewBody = Replace(obj.HTMLBody, strDelete03, "")
NewBody = Replace(obj.HTMLBody, strDelete04, "")
If NewBody <> "" Then
Each Replace (except the first) overwrites the value of NewBody created by the previous Replace. You seem to think that if strDelete04 is not found, NewBody will be empty. No, if strDelete04 is not found, NewBody will be a copy of obj.HTMLBody.
You need something like:
NewBody = Replace(obj.HTMLBody, strDelete01, "")
NewBody = Replace(NewBody, strDelete02, "")
NewBody = Replace(NewBody, strDelete03, "")
NewBody = Replace(NewBody, strDelete04, "")
If NewBody <> obj.HTMLBody Then
' One or more delete strings found and removed
You say that the CRLFs are not in fixed positions. If so, no simple modification of your code will have the effect you seek. I will show you how to achieve the effect you seek but first I will have to create some emails containing your text so I can test my code.
Part 2
Having looked at your image of the Html more closely, I believe there is a simple solution. The two CRLFs in the text replace spaces. Providing this is always what happens, you can use:
NewBody = Replace(obj.HTMLBody, vbCr & vbLf, " ")
This would remove any CRLF present wherever it appeared within the Html. It would not matter if there were extra CRLFs because any string of whitespace characters (which includes CR and LF) in an Html document is replaced by a single space when the document is displayed.
You finish the removal of the unwanted text with:
Dim strDelete = "Diese E-Mail kommt von Personen außerhalb " & _
"der Stadtverwaltung. Klicken Sie nur auf " & _
"Links oder Dateianhänge, wenn Sie die Personen " & _
"für vertrauenswürdig halten."
NewBody = Replace(NewBody, strDelete, "")
If the above does not work, you need a more convenient diagnostic technique. Saving the entire email as Html may be easy but you cannot be quite sure how the result differs from what a VBA macro would see. You wonder if Outlook stores emails in a format other than Html. I cannot imagine why Outlook would convert the incoming SMTP message to some secret format and then convert it back when the user wishes to view it. If Outlook does have a secret format, it is totally hidden from the VBA programmer.
The following is a simple version of the diagnostic tool I use. If you need something more advanced, I can provide it but let us try this first.
Copy the code below to an Outlook module. Select one of these emails and then run macro DsplHtmlBodyFromSelectedEmails. The entire Html body of the email will be output to the Immediate Window in a readable format. I believe I have included all the subroutines called by the macro. I apologise in advance if I have not. If you get a message about an undefined routine, let me know and I will add it to the answer.
Sub DsplHtmlBodyFromSelectedEmails()
' Select one or emails then run this macro. For each selected email, the Received Time, the Subject and the Html body are output to the Immediate Window. Note: the Immediate Window can only display about 200 lines before
The older lines are lost.
Dim Exp As Explorer
Dim Html As String
Dim ItemCrnt As MailItem
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Call MsgBox("Please select one or more emails then try again", vbOKOnly)
Exit Sub
Else
For Each ItemCrnt In Exp.Selection
With ItemCrnt
If .Class = olMail Then
Debug.Print .ReceivedTime & " " & .Subject
Call OutLongTextRtn(Html, "Html", .HtmlBody)
Debug.Print Html
End If
End With
Next
End If
End Sub
Sub OutLongTextRtn(ByRef TextOut As String, ByVal Head As String, _
ByVal TextIn As String)
' * Break TextIn into lines of not more than 100 characters
' and append to TextOut.
' * The output is arranged so:
' xxxx|sssssssssssssss|
' |sssssssssssssss|
' |ssssssssss|
' where "xxxx" is the value of Head and "ssss..." are characters from
' TextIn. The third line in the example could be shorter because:
' * it contains the last few characters of TextIn
' * there a linefeed in TextIn
' * a <xxx> string recording whitespace would have been split
' across two lines.
If TextIn = "" Then
' Nothing to do
Exit Sub
End If
Const LenLineMax As Long = 100
Dim PosBrktEnd As Long ' Last > before PosEnd
Dim PosBrktStart As Long ' Last < before PosEnd
Dim PosNext As Long ' Start of block to be output after current block
Dim PosStart As Long ' First character of TextIn not yet output
TextIn = TidyTextForDspl(TextIn)
TextIn = Replace(TextIn, "lf›", "lf›" & vbLf)
PosStart = 1
Do While True
PosNext = InStr(PosStart, TextIn, vbLf)
If PosNext = 0 Then
' No LF in [Remaining] TextIn
'Debug.Assert False
PosNext = Len(TextIn) + 1
End If
If PosNext - PosStart > LenLineMax Then
PosNext = PosStart + LenLineMax
End If
' Check for <xxx> being split across lines
PosBrktStart = InStrRev(TextIn, "‹", PosNext - 1)
PosBrktEnd = InStrRev(TextIn, "›", PosNext - 1)
If PosBrktStart < PosStart And PosBrktEnd < PosStart Then
' No <xxx> within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And PosBrktEnd > 0 And PosBrktEnd > PosBrktStart Then
' Last or only <xxx> totally within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And _
(PosBrktEnd = 0 Or (PosBrktEnd > 0 And PosBrktEnd < PosBrktStart)) Then
' Last or only <xxx> will be split across rows
'Debug.Assert False
PosNext = PosBrktStart
Else
' Are there other combinations?
Debug.Assert False
End If
'Debug.Assert Right$(Mid$(TextIn, PosStart, PosNext - PosStart), 1) <> "‹"
If TextOut <> "" Then
TextOut = TextOut & vbLf
End If
If PosStart = 1 Then
TextOut = TextOut & Head & "|"
Else
TextOut = TextOut & Space(Len(Head)) & "|"
End If
TextOut = TextOut & Mid$(TextIn, PosStart, PosNext - PosStart) & "|"
PosStart = PosNext
If Mid$(TextIn, PosStart, 1) = vbLf Then
PosStart = PosStart + 1
End If
If PosStart > Len(TextIn) Then
Exit Do
End If
Loop
End Sub
Function TidyTextForDspl(ByVal Text As String) As String
' Tidy Text for display by replacing white space with visible strings:
' Leave single space unchanged
' Replace single LF by ‹lf›
' Replace single CR by ‹cr›
' Replace single TB by ‹tb›
' Replace single non-break space by ‹nbs›
' Replace single CRLF by ‹crlf›
' Replace multiple spaces by ‹n s› where n is number of repeats
' Replace multiple LFs by ‹n lf› of white space character
' Replace multiple CRs by ‹cr› or ‹n cr›
' Replace multiple TBs by ‹n tb›
' Replace multiple non-break spaces by ‹n nbs›
' Replace multiple CRLFs by ‹n crlf›
Dim InsStr As String
Dim InxWsChar As Long
Dim NumWsChar As Long
Dim PosWsChar As Long
Dim RetnVal As String
Dim WsCharCrnt As Variant
Dim WsCharValue As Variant
Dim WsCharDspl As Variant
WsCharValue = VBA.Array(" ", vbCr & vbLf, vbLf, vbCr, vbTab, Chr(160))
WsCharDspl = VBA.Array("s", "crlf", "lf", "cr", "tb", "nbs")
RetnVal = Text
' Replace each whitespace individually
For InxWsChar = 0 To UBound(WsCharValue)
RetnVal = Replace(RetnVal, WsCharValue(InxWsChar), "‹" & WsCharDspl(InxWsChar) & "›")
Next
' Look for repeats. If found replace <x> by <n x>
For InxWsChar = 0 To UBound(WsCharValue)
'Debug.Assert InxWsChar <> 1
PosWsChar = 1
Do While True
InsStr = "‹" & WsCharDspl(InxWsChar) & "›"
PosWsChar = InStr(PosWsChar, RetnVal, InsStr & InsStr)
If PosWsChar = 0 Then
' No [more] repeats of this <x>
Exit Do
End If
' Have <x><x>. Count number of extra <x>s
NumWsChar = 2
Do While Mid(RetnVal, PosWsChar + NumWsChar * Len(InsStr), Len(InsStr)) = InsStr
NumWsChar = NumWsChar + 1
Loop
RetnVal = Mid(RetnVal, 1, PosWsChar - 1) & _
"‹" & NumWsChar & " " & WsCharDspl(InxWsChar) & "›" & _
Mid(RetnVal, PosWsChar + NumWsChar * Len(InsStr))
PosWsChar = PosWsChar + Len(InsStr) + Len(NumWsChar)
Loop
Next
' Restore any single spaces
RetnVal = Replace(RetnVal, "‹" & WsCharDspl(0) & "›", " ")
TidyTextForDspl = RetnVal
End Function

Where to find property value "e-mail-account" of mailitem-objects in outlook object-model?

The property could not be found analysing all elements of this list (and subobjects):
https://learn.microsoft.com/en-us/office/vba/api/outlook.mailitem.actions
Some property-values (like size or body) are directly accessable.
Other values (like recipients) must be extracted from the stored subobjects.
But all retrieved values were not corresponding to the visible data in the outlook-column.
It's not a userproperty.
The column can be inserted into the outllok e-mail table-view via the fieldlist.
I suppose, that the data stored in the field/column "e-mail-account", was inserted on sending from the property "SendUsingAccount", but this property seems to not be accessible on received emails.
How can this property be accessed/edited in received emails?
Field Chooser/...
...All Mail Fields/E-Mail account
Where else to search?
Because of this description, I originally thought "SendUsingAccount" might be the data source: "...Returns or sets an Account object that represents the account under which the MailItem is to be sent. Read/write..."
But now I know, the string comes from here, when a new account is created (there may be other ways)
You can access that property using MailItem.PropertyAccessor.GetProperty() specifying the DASL name displayed by OutlookSpy – Dmitry Streblechenko
MailItem.PropertyAccessor.GetProperty("schemas.microsoft.com/mapi/id{00062008-0000-0000-C000-000000000046}/8580001F")
I do not have “E-Mail Account” in any of my views and I do not wish to change any of them to understand what this column would contain. I suspect it is not a single property but one that depends on the context.
I do not understand why you expect property "SendUsingAccount" to appear in the received email. If an assistant sends an email in the name of a manager, I would expect the manager’s name and email address to appear in the sender properties. I would not expect to find the assistant’s name anywhere.
I use Explorer to investigate emails. To use Explorer, the user selects one or more emails and then calls a macro that processes the selected emails. The macros I use for investigations either output a small number of properties to the Immediate Window or every property I have ever been interested in to a desktop file.
I have tidied my routines so I can include both versions without too much duplication.
Note: these routines need references to “Microsoft Scripting Runtime” and "Microsoft ActiveX Data Objects n.n Library". The n.n is probably “6.1” but use whatever version you have. If you do not understand “references”, ask and I will explain.
Macro InvestigateEmails is the macro you call after selecting one or more emails you wish to investigate. Within the macro is the statement #Const Selected = True. This instructs the macro to call macro OutSomeProperties to perform the output. If you change the statement to #Const Selected = False it will call macro OutAllProperties.
Macro OutSomeProperties outputs a small number of properties to the Immediate Window.
Macro OutAllProperties outputs every property that I have ever been interested in. In particular, it includes the entire message header. If the value you seek is not in the message header, it is not available to Outlook.
Following these macros are a number of “standard” routines. I hold these standard routines in their own modules. I believe I have included ever standard routine called by the first three macros. If I haven’t, you will get an error message telling you xxxx cannot be found. Report this error in a comment and I will add the missing routine to to my answer.
Run macro InvestigateEmails as is. Is the value you seek output to the Immediate Window? If not, amend InvestigateEmails to call OutputAllProperties. Look at the text under “PR_TRANSPORT_MESSAGE_HEADERS”. Is the value you seek here? If it is, report the relevant line in a comment and I will help you extract the property you seek.
Option Explicit
Public Sub InvestigateEmails()
' Outputs all or selected properties of one or more emails.
' To use:
' * Set "Selected" to True or False as required.
' * If Selected=True, review OutSomeProperties to ensure it
' outputs the properties of interest.
' * If Selected=False, review OutAllProperties to ensure it
' outputs the properties of interest.
' * Select one or more emails from a folder.
' * Run this subroutine.
' ========================================================================
' "Selected = True" to output a small number of properties for
' a small number of emails to the Immediate Window.
' "Selected = False" to output all properties for any number of emails
' to desktop file "InvestigateEmails.txt".
#Const Selected = True
' ========================================================================
' Technique for locating desktop from answer by Kyle:
' http://stackoverflow.com/a/17551579/973283
' Needs reference to "Microsoft Scripting Runtime"
Dim Exp As Explorer
Dim ItemCrnt As MailItem
#If Not Selected Then
Dim FileBody As String
Dim fso As FileSystemObject
Dim Path As String
Path = CreateObject("WScript.Shell").SpecialFolders("Desktop")
#End If
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Call MsgBox("Please select one or more emails then try again", vbOKOnly)
Exit Sub
Else
For Each ItemCrnt In Exp.Selection
If ItemCrnt.Class = olMail Then
#If Selected Then
Call OutSomeProperties(ItemCrnt)
#Else
Call OutAllProperties(ItemCrnt, FileBody)
#End If
End If
Next
End If
#If Not Selected Then
Call PutTextFileUtf8NoBom(Path & "\InvestigateEmails.txt", FileBody)
#End If
End Sub
Sub OutSomeProperties(ItemCrnt As Outlook.MailItem)
' Outputs selected properties of a MailItem to the Immediate Window.
' The Immediate Window can only display about 200 rows before the older
' rows start scrolling off the top. This means this routine is only
' suitable for displaying a small number of simple properties. Add or
' remove properties as necessary to meet the current requirement.
Dim InxR As Long
Debug.Print "=============================================="
Debug.Print " Profile: " & Session.CurrentProfileName
Debug.Print " User: " & Session.CurrentUser
With ItemCrnt
Debug.Print " Created: " & .CreationTime
Debug.Print " Receiver: " & .ReceivedByName
Debug.Print " Received: " & .ReceivedTime
For InxR = 1 To .Recipients.Count
Debug.Print "Recipient: " & .Recipients(InxR)
Next
Debug.Print " Sender: " & .Sender
Debug.Print " SenderEA: " & .SenderEmailAddress
Debug.Print " SenderNm: " & .SenderName
Debug.Print " SentOn: " & .SentOn
Debug.Print " Subject: " & .Subject
Debug.Print " To: " & .To
End With
End Sub
Sub OutAllProperties(ItemCrnt As Outlook.MailItem, ByRef FileBody As String)
' Adds all properties of a MailItem to FileBody.
' The phrase "all properties" should more correctly be "all properties
' that I know of and have ever been interested in".
' Source of PropertyAccessor information:
' https://www.slipstick.com/developer/read-mapi-properties-exposed-outlooks-object-model/
' 17Apr19 Created by combining a number of earlier routine which output
' different sets of properties to a file
Dim InxA As Long
Dim InxR As Long
Dim PropAccess As Outlook.propertyAccessor
If FileBody <> "" Then
FileBody = FileBody & String(80, "=") & vbLf
End If
With ItemCrnt
FileBody = FileBody & "From (Sender): " & .Sender
FileBody = FileBody & vbLf & "From (Sender name): " & .SenderName
FileBody = FileBody & vbLf & "From (Sender email address): " & _
.SenderEmailAddress
FileBody = FileBody & vbLf & "Subject: " & CStr(.Subject)
FileBody = FileBody & vbLf & "Received: " & Format(.ReceivedTime, "dmmmyy hh:mm:ss")
FileBody = FileBody & vbLf & "To: " & .To
FileBody = FileBody & vbLf & "CC: " & .CC
FileBody = FileBody & vbLf & "BCC: " & .BCC
If .Attachments.Count = 0 Then
FileBody = FileBody & vbLf & "No attachments"
Else
FileBody = FileBody & vbLf & "Attachments:"
FileBody = FileBody & vbLf & "No.|Type|Path|Filename|DisplayName|"
For InxR = 1 To .Recipients.Count
FileBody = FileBody & vbLf & "Recipient" & InxR & ": " & .Recipients(InxR)
Next
For InxA = 1 To .Attachments.Count
With .Attachments(InxA)
FileBody = FileBody & vbLf & InxA & "|"
Select Case .Type
Case olByValue
FileBody = FileBody & "Val"
Case olEmbeddeditem
FileBody = FileBody & "Ebd"
Case olByReference
FileBody = FileBody & "Ref"
Case olOLE
FileBody = FileBody & "OLE"
Case Else
FileBody = FileBody & "Unk"
End Select
' Not all types have all properties. This code handles
' those missing properties of which I am aware. However,
' I have never found an attachment of type Reference or OLE.
' Additional code may be required for them.
Select Case .Type
Case olEmbeddeditem
FileBody = FileBody & "|"
Case Else
FileBody = FileBody & "|" & .Pathname
End Select
FileBody = FileBody & "|" & .Filename
FileBody = FileBody & "|" & .DisplayName & "|"
End With
Next
End If ' .Attachments.Count = 0
Call OutLongTextRtn(FileBody, "Text: ", .Body)
Call OutLongTextRtn(FileBody, "Html: ", .HtmlBody)
Set PropAccess = .propertyAccessor
FileBody = FileBody & vbLf & "PR_RECEIVED_BY_NAME: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0040001E")
FileBody = FileBody & vbLf & "PR_SENT_REPRESENTING_NAME: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0042001E")
FileBody = FileBody & vbLf & "PR_REPLY_RECIPIENT_NAMES: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0050001E")
FileBody = FileBody & vbLf & "PR_SENT_REPRESENTING_EMAIL_ADDRESS: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0065001E")
FileBody = FileBody & vbLf & "PR_RECEIVED_BY_EMAIL_ADDRESS: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0076001E")
FileBody = FileBody & vbLf & "PR_TRANSPORT_MESSAGE_HEADERS:" & vbLf & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E")
FileBody = FileBody & vbLf & "PR_SENDER_NAME: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0C1A001E")
FileBody = FileBody & vbLf & "PR_SENDER_EMAIL_ADDRESS: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0C1F001E")
FileBody = FileBody & vbLf & "PR_DISPLAY_BCC: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0E02001E")
FileBody = FileBody & vbLf & "PR_DISPLAY_CC: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0E03001E")
FileBody = FileBody & vbLf & "PR_DISPLAY_TO: " & _
PropAccess.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0E04001E")
Set PropAccess = Nothing
End With
End Sub
Sub OutLongTextRtn(ByRef TextOut As String, ByVal Head As String, _
ByVal TextIn As String)
' * Break TextIn into lines of not more than 100 characters
' and append to TextOut.
' * The output is arranged so:
' xxxx|sssssssssssssss|
' |sssssssssssssss|
' |ssssssssss|
' where "xxxx" is the value of Head and "ssss..." are characters from
' TextIn. The third line in the example could be shorter because:
' * it contains the last few characters of TextIn
' * there a linefeed in TextIn
' * a <xxx> string recording whitespace would have been split
' across two lines.
' 15Jan19 Added "|" at start and end of lines to make it clearer if
' whitespace added by this routine or was in original TextIn
' 3Feb19 Discovered I had two versions of OutLongText. Renamed this version to
' indicate it returned a formatted string.
' 4Feb19 Previous version relied on the caller tidying text for display. This
' version expects TextIn to be untidied and uses TidyTextForDspl to tidy
' the text and then creates TextOut from its output.
If TextIn = "" Then
' Nothing to do
Exit Sub
End If
Const LenLineMax As Long = 100
Dim PosBrktEnd As Long ' Last > before PosEnd
Dim PosBrktStart As Long ' Last < before PosEnd
Dim PosNext As Long ' Start of block to be output after current block
Dim PosStart As Long ' First character of TextIn not yet output
TextIn = TidyTextForDspl(TextIn)
TextIn = Replace(TextIn, "lf›", "lf›" & vbLf)
PosStart = 1
Do While True
PosNext = InStr(PosStart, TextIn, vbLf)
If PosNext = 0 Then
' No LF in [Remaining] TextIn
'Debug.Assert False
PosNext = Len(TextIn) + 1
End If
If PosNext - PosStart > LenLineMax Then
PosNext = PosStart + LenLineMax
End If
' Check for <xxx> being split across lines
PosBrktStart = InStrRev(TextIn, "‹", PosNext - 1)
PosBrktEnd = InStrRev(TextIn, "›", PosNext - 1)
If PosBrktStart < PosStart And PosBrktEnd < PosStart Then
' No <xxx> within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And PosBrktEnd > 0 And PosBrktEnd > PosBrktStart Then
' Last or only <xxx> totally within text to be displayed
' No change to PosNext
'Debug.Assert False
ElseIf PosBrktStart > 0 And _
(PosBrktEnd = 0 Or (PosBrktEnd > 0 And PosBrktEnd < PosBrktStart)) Then
' Last or only <xxx> will be split across rows
'Debug.Assert False
PosNext = PosBrktStart
Else
' Are there other combinations?
Debug.Assert False
End If
'Debug.Assert Right$(Mid$(TextIn, PosStart, PosNext - PosStart), 1) <> "‹"
If TextOut <> "" Then
TextOut = TextOut & vbLf
End If
If PosStart = 1 Then
TextOut = TextOut & Head & "|"
Else
TextOut = TextOut & Space(Len(Head)) & "|"
End If
TextOut = TextOut & Mid$(TextIn, PosStart, PosNext - PosStart) & "|"
PosStart = PosNext
If Mid$(TextIn, PosStart, 1) = vbLf Then
PosStart = PosStart + 1
End If
If PosStart > Len(TextIn) Then
Exit Do
End If
Loop
End Sub
Sub PutTextFileUtf8NoBom(ByVal PathFileName As String, ByVal FileBody As String)
' Outputs FileBody as a text file named PathFileName using
' UTF-8 encoding without leading BOM
' Needs reference to "Microsoft ActiveX Data Objects n.n Library"
' Addition to original code says version 2.5. Tested with version 6.1.
' 1Nov16 Copied from http://stackoverflow.com/a/4461250/973283
' but replaced literals with parameters.
' 15Aug17 Discovered routine was adding an LF to the end of the file.
' Added code to discard that LF.
' 11Oct17 Posted to StackOverflow
' 9Aug18 Comment from rellampec suggested removal of adWriteLine from
' WriteTest statement would avoid adding LF.
' 30Sep18 Amended routine to remove adWriteLine from WriteTest statement
' and code to remove LF from file. Successfully tested new version.
' References: http://stackoverflow.com/a/4461250/973283
' https://www.w3schools.com/asp/ado_ref_stream.asp
Dim BinaryStream As Object
Dim UTFStream As Object
Set UTFStream = CreateObject("adodb.stream")
UTFStream.Type = adTypeText
UTFStream.Mode = adModeReadWrite
UTFStream.Charset = "UTF-8"
UTFStream.Open
UTFStream.WriteText FileBody
UTFStream.Position = 3 'skip BOM
Set BinaryStream = CreateObject("adodb.stream")
BinaryStream.Type = adTypeBinary
BinaryStream.Mode = adModeReadWrite
BinaryStream.Open
UTFStream.CopyTo BinaryStream
UTFStream.Flush
UTFStream.Close
Set UTFStream = Nothing
BinaryStream.SaveToFile PathFileName, adSaveCreateOverWrite
BinaryStream.Flush
BinaryStream.Close
Set BinaryStream = Nothing
End Sub
Function TidyTextForDspl(ByVal Text As String) As String
' Tidy Text for dsplay by replacing white space with visible strings:
' Leave single space unchanged
' Replace single LF by ‹lf›
' Replace single CR by ‹cr›
' Replace single TB by ‹tb›
' Replace single non-break space by ‹nbs›
' Replace single CRLF by ‹crlf›
' Replace multiple spaces by ‹n s› where n is number of repeats
' Replace multiple LFs by ‹n lf› of white space character
' Replace multiple CRs by ‹cr› or ‹n cr›
' Replace multiple TBs by ‹n tb›
' Replace multiple non-break spaces by ‹n nbs›
' Replace multiple CRLFs by ‹n crlf›
' 15Mar16 Coded
' 3Feb19 Replaced "{" (\x7B) and "}" (\x7D) by "‹" (\u2039) and "›" (\u203A)
' on the grounds that the angle quotation marks were not likely to
' appear in text to be displayed.
' 5Feb19 Add code to treat CRLF as unit
' 28Mar19 Code to calculate PosWsChar after "<x>...<x>" converted to "<n x>"
' incorrect if "<x>...<x>" at the start of the string. Unlikely it
' was correct in other situations but this did not matter since the
' calculated value would be before the next occurrence of "<x>...<x>".
' But, if the string was near the beginning of the string, the
' calculated value was negative and the code crashed.
Dim InsStr As String
Dim InxWsChar As Long
Dim NumWsChar As Long
Dim PosWsChar As Long
Dim RetnVal As String
Dim WsCharCrnt As Variant
Dim WsCharValue As Variant
Dim WsCharDspl As Variant
WsCharValue = VBA.Array(" ", vbCr & vbLf, vbLf, vbCr, vbTab, Chr(160))
WsCharDspl = VBA.Array("s", "crlf", "lf", "cr", "tb", "nbs")
RetnVal = Text
' Replace each whitespace individually
For InxWsChar = 0 To UBound(WsCharValue)
RetnVal = Replace(RetnVal, WsCharValue(InxWsChar), "‹" & WsCharDspl(InxWsChar) & "›")
Next
' Look for repeats. If found replace <x> by <n x>
For InxWsChar = 0 To UBound(WsCharValue)
'Debug.Assert InxWsChar <> 1
PosWsChar = 1
Do While True
InsStr = "‹" & WsCharDspl(InxWsChar) & "›"
PosWsChar = InStr(PosWsChar, RetnVal, InsStr & InsStr)
If PosWsChar = 0 Then
' No [more] repeats of this <x>
Exit Do
End If
' Have <x><x>. Count number of extra <x>x
NumWsChar = 2
Do While Mid(RetnVal, PosWsChar + NumWsChar * Len(InsStr), Len(InsStr)) = InsStr
NumWsChar = NumWsChar + 1
Loop
RetnVal = Mid(RetnVal, 1, PosWsChar - 1) & _
"‹" & NumWsChar & " " & WsCharDspl(InxWsChar) & "›" & _
Mid(RetnVal, PosWsChar + NumWsChar * Len(InsStr))
PosWsChar = PosWsChar + Len(InsStr) + Len(NumWsChar)
Loop
Next
' Restore any single spaces
RetnVal = Replace(RetnVal, "‹" & WsCharDspl(0) & "›", " ")
TidyTextForDspl = RetnVal
End Function

An app operating under Windows 10 finds a byteOrderMarkUtf8 in a string, but there isn't one in the string

I have an app that receives an XML string and tries to clean it up before processing. For some reason, under the Windows 10 operating system, the app thinks there is a byteOrderMarkUtf8 leading the string. There isn't one.
The first character is "<". The app removes the "<", and then removes the rest of the tag, too, creating an invalid XML.
This used to work under Windows 7.
In the code below, I have commented the offending section out.
Is there something about character encoding that has changed with Windows 10 that would be causing this?
Private Sub CleanXML(ByRef InString As String)
' This subroutine cleans trash characters out of XML streams
If (InString = "") Then
MessageBox.Show("Null String passed to CleanXML." & vbCr & _
"String Length: " & InString.Length & vbCr & _
"Instring: " & InString & vbCr)
End If
If (InString.Length = 0) Then
MessageBox.Show("String of 0 length or null String passed to CleanXML." & vbCr & _
"String Length: " & InString.Length & vbCr & _
"Instring: " & InString & vbCr)
End If
Dim CleanString As String = InString
CleanString = CleanString.Trim() ' Trim leading and trailing spaces
CleanString = CleanString.Replace("- ", "") ' Replace the dashes
CleanString = CleanString.Replace(" <", "<") ' Replace some white space
CleanString = CleanString.Replace(" <", "<") ' Replace some white space
CleanString = CleanString.Replace("-<", "<") ' Replace dash+lessthan with lessthan
CleanString = CleanString.Replace("- <", "<") ' Replace dash+space+lessthan with lessthan
CleanString = CleanString.Replace("&", "&") ' Replace & with &
Dim Tempstring As String = ""
If CleanString.Length > 0 Then
Try
Dim byteOrderMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble())
' This is the offending code that I have commented out.
'-------------------------------------------------------------
'If (CleanString.StartsWith(byteOrderMarkUtf8)) Then
' CleanString = CleanString.Remove(0, byteOrderMarkUtf8.Length)
'End If
'If (CleanString.EndsWith(byteOrderMarkUtf8)) Then
' CleanString = CleanString.Remove(CleanString.Length - 1, byteOrderMarkUtf8.Length)
'End If
'-------------------------------------------------------------
' Make sure the first and last characters are "<" and ">".
Tempstring = CleanString
Do Until (CleanString.StartsWith("<") Or (CleanString.Length = 0))
CleanString = CleanString.Remove(0, 1)
Loop
Do Until (CleanString.EndsWith(">") Or (CleanString.Length = 0))
CleanString = CleanString.Remove(CleanString.Length - 1, 1)
Loop
Catch ex As Exception
MessageBox.Show("Error in CleanXML." & vbCr & _
"String Length: " & CleanString.Length & vbCr & _
"Instring: " & InString & vbCr & _
"CleanString: " & CleanString & _
" Length: " & CleanString.Length.ToString)
MessageBox.Show(ex.Message + " Inner exception: " + ex.InnerException.Message)
MessageBox.Show(Tempstring)
End Try
Else
MessageBox.Show("Clean string of 0 length in CleanXML." & vbCr & _
"String Length: " & CleanString.Length & vbCr & _
"Instring: " & InString & vbCr & _
"CleanString: " & CleanString)
End If
' Remove any BOMs (Byte-Order Marks) from the string.
'Dim i As Integer = InStr(1, CleanString, byteOrderMarkUtf8)
'Do Until i = 0
' CleanString = CleanString.Remove(i - 1, byteOrderMarkUtf8.Length)
' i = InStr(i, CleanString, byteOrderMarkUtf8)
'Loop
InString = CleanString
End Sub

Crop last N lines of a string to display in userform textbox

I want to display a textlog string in a userform's textbox.
Code might look like this:
Dim public textlog as string
sub button1_click()
' do some action
textlog = textlog & event_string & vbCrLf
'event_string might exceed more than 2 line
textlog = textlog & "button1 action" & vbCrLf
userform1.textbox1.text = textlog
end sub
sub button2_click()
' do some action
textlog = textlog & event_string & vbCrLf
'event_string might exceed more than 2 line
textlog = textlog & "button2 action" & vbCrLf
userform1.textbox1.text = textlog
end sub
However, the textbox should only contain 20 lines of information, while my
the contents of my textlog will exceed 20 lines.
How can I display only the latest (last) 20 lines of the textlog in textbox1?
You can use this function to return only the last N lines of a string, and then display that in your textbox.
Note that you have to specify what the line break character is. Depending on your specific application, it could be vbCrLf, vbCr, vbLf, or even some other delimiter.
Function GetLastLines(ByVal s As String, ByVal nLinesToDisplay As Long, _
Optional ByVal lineBreakChar As String = vbCrLf)
'Split the string into an array
Dim splitString() As String
splitString = Split(s, lineBreakChar)
'How many lines are there?
Dim nLines As Long
nLines = UBound(splitString) + 1
If nLines <= nLinesToDisplay Then
'No need to remove anything. Get out.
GetLastLines = s
Exit Function
End If
'Collect last N lines in a new array
Dim lastLines() As String
ReDim lastLines(0 To nLinesToDisplay - 1)
Dim i As Long
For i = 0 To UBound(lastLines)
lastLines(i) = splitString(i + nLines - nLinesToDisplay)
Next i
'Join the lines array into a single string
GetLastLines = Join(lastLines, lineBreakChar)
End Function
Example usage:
MsgBox GetLastLines( _
"line 1" & vbCrLf & "line 2" & vbCrLf & "line 3" & vbCrLf _
& "line 4" & vbCrLf & "line 5" & vbCrLf & "line 6", _
4, vbCrLf)
Only the last 4 lines are displayed:
Note that this assumes that your last line is not terminated by a line break. If it is, then you can tweak the code to deal with that.
Alternatively, you can use Excel's built-in SUBSTITUTE function, which is useful in this particular case, because it can locate a specific instance of a given character. So instead of building arrays you can use a one-liner:
Function GetLastLines2(ByVal s As String, ByVal nLinesToDisplay As Long, _
Optional ByVal lineBreakChar As String = vbCrLf)
'An arbitrary character that will never be in your input string:
Dim delim As String: delim = Chr(1)
'How many lines are there?
Dim nLines As Long
nLines = UBound(Split(s, lineBreakChar)) + 1
If nLines <= nLinesToDisplay Then
'No need to remove anything. Get out.
GetLastLines2 = s
Exit Function
End If
'Replace one line break with delim, split the string on it,
'return only second part:
GetLastLines2 = Split( _
WorksheetFunction.Substitute( _
s, lineBreakChar, delim, nLines - nLinesToDisplay), _
delim)(1)
End Function
A = "Cat" & vbcrlf & "Tiger" & vbcrlf & "Lion" & vbcrlf & "Shark hunting florida lynxs" & vbcrlf & "Leopard" & vbcrlf & "Cheetah"
A= StrReverse(A)
NumLines = 3
i=1
For X = 1 to NumLines
i = Instr(i, A, vbcr) + 1
Next
Msgbox StrReverse(Left(A, i - 1))
This is a program that cuts or leaves lines from top or bottom of files.
To use
Cut
filter cut {t|b} {i|x} NumOfLines
Cuts the number of lines from the top or bottom of file.
t - top of the file
b - bottom of the file
i - include n lines
x - exclude n lines
Example
cscript //nologo filter.vbs cut t i 5 < "%systemroot%\win.ini"
The script
Set rs = CreateObject("ADODB.Recordset")
With rs
.Fields.Append "LineNumber", 4
.Fields.Append "Txt", 201, 5000
.Open
LineCount = 0
Do Until Inp.AtEndOfStream
LineCount = LineCount + 1
.AddNew
.Fields("LineNumber").value = LineCount
.Fields("Txt").value = Inp.readline
.UpDate
Loop
.Sort = "LineNumber ASC"
If LCase(Arg(1)) = "t" then
If LCase(Arg(2)) = "i" then
.filter = "LineNumber < " & LCase(Arg(3)) + 1
ElseIf LCase(Arg(2)) = "x" then
.filter = "LineNumber > " & LCase(Arg(3))
End If
ElseIf LCase(Arg(1)) = "b" then
If LCase(Arg(2)) = "i" then
.filter = "LineNumber > " & LineCount - LCase(Arg(3))
ElseIf LCase(Arg(2)) = "x" then
.filter = "LineNumber < " & LineCount - LCase(Arg(3)) + 1
End If
End If
Do While not .EOF
Outp.writeline .Fields("Txt").Value
.MoveNext
Loop
End With

Find the directory part (minus the filename) of a full path in access 97

For various reasons, I'm stuck in Access 97 and need to get only the path part of a full pathname.
For example, the name
c:\whatever dir\another dir\stuff.mdb
should become
c:\whatever dir\another dir\
This site has some suggestions on how to do it:
http://www.ammara.com/access_image_faq/parse_path_filename.html
But they seem rather hideous. There must be a better way, right?
You can do something simple like: Left(path, InStrRev(path, "\"))
Example:
Function GetDirectory(path)
GetDirectory = Left(path, InStrRev(path, Application.PathSeparator))
End Function
I always used the FileSystemObject for this sort of thing. Here's a little wrapper function I used. Be sure to reference the Microsoft Scripting Runtime.
Function StripFilename(sPathFile As String) As String
'given a full path and file, strip the filename off the end and return the path
Dim filesystem As New FileSystemObject
StripFilename = filesystem.GetParentFolderName(sPathFile) & "\"
Exit Function
End Function
This seems to work. The above doesn't in Excel 2010.
Function StripFilename(sPathFile As String) As String
'given a full path and file, strip the filename off the end and return the path
Dim filesystem As Object
Set filesystem = CreateObject("Scripting.FilesystemObject")
StripFilename = filesystem.GetParentFolderName(sPathFile) & "\"
Exit Function
End Function
If you're just needing the path of the MDB currently open in the Access UI, I'd suggest writing a function that parses CurrentDB.Name and then stores the result in a Static variable inside the function. Something like this:
Public Function CurrentPath() As String
Dim strCurrentDBName As String
Static strPath As String
Dim i As Integer
If Len(strPath) = 0 Then
strCurrentDBName = CurrentDb.Name
For i = Len(strCurrentDBName) To 1 Step -1
If Mid(strCurrentDBName, i, 1) = "\" Then
strPath = Left(strCurrentDBName, i)
Exit For
End If
Next
End If
CurrentPath = strPath
End Function
This has the advantage that it only loops through the name one time.
Of course, it only works with the file that's open in the user interface.
Another way to write this would be to use the functions provided at the link inside the function above, thus:
Public Function CurrentPath() As String
Static strPath As String
If Len(strPath) = 0 Then
strPath = FolderFromPath(CurrentDB.Name)
End If
CurrentPath = strPath
End Function
This makes retrieving the current path very efficient while utilizing code that can be used for finding the path for any filename/path.
vFilename="C:\Informes\Indicadores\Program\Ind_Cont_PRv.txt"
vDirFile = Replace(vFilename, Dir(vFileName, vbDirectory), "")
' Result=C:\Informes\Indicadores_Contraloria\Programa\Versiones anteriores\
left(currentdb.Name,instr(1,currentdb.Name,dir(currentdb.Name))-1)
The Dir function will return only the file portion of the full path. Currentdb.Name is used here, but it could be any full path string.
If you are confident in your input parameters, you can use this single line of code which uses the native Split and Join functions and Excel native Application.pathSeparator.
Split(Join(Split(strPath, "."), Application.pathSeparator), Application.pathSeparator)
If you want a more extensive function, the code below is tested in Windows and should also work on Mac (though not tested). Be sure to also copy the supporting function GetPathSeparator, or modify the code to use Application.pathSeparator. Note, this is a first draft; I should really refactor it to be more concise.
Private Sub ParsePath2Test()
'ParsePath2(DrivePathFileExt, -2) returns a multi-line string for debugging.
Dim p As String, n As Integer
Debug.Print String(2, vbCrLf)
If True Then
Debug.Print String(2, vbCrLf)
Debug.Print ParsePath2("", -2)
Debug.Print ParsePath2("C:", -2)
Debug.Print ParsePath2("C:\", -2)
Debug.Print ParsePath2("C:\Windows", -2)
Debug.Print ParsePath2("C:\Windows\notepad.exe", -2)
Debug.Print ParsePath2("C:\Windows\SysWOW64", -2)
Debug.Print ParsePath2("C:\Windows\SysWOW64\", -2)
Debug.Print ParsePath2("C:\Windows\SysWOW64\AcLayers.dll", -2)
Debug.Print ParsePath2("C:\Windows\SysWOW64\.fakedir", -2)
Debug.Print ParsePath2("C:\Windows\SysWOW64\fakefile.ext", -2)
End If
If True Then
Debug.Print String(1, vbCrLf)
Debug.Print ParsePath2("\Windows", -2)
Debug.Print ParsePath2("\Windows\notepad.exe", -2)
Debug.Print ParsePath2("\Windows\SysWOW64", -2)
Debug.Print ParsePath2("\Windows\SysWOW64\", -2)
Debug.Print ParsePath2("\Windows\SysWOW64\AcLayers.dll", -2)
Debug.Print ParsePath2("\Windows\SysWOW64\.fakedir", -2)
Debug.Print ParsePath2("\Windows\SysWOW64\fakefile.ext", -2)
End If
If True Then
Debug.Print String(1, vbCrLf)
Debug.Print ParsePath2("Windows\notepad.exe", -2)
Debug.Print ParsePath2("Windows\SysWOW64", -2)
Debug.Print ParsePath2("Windows\SysWOW64\", -2)
Debug.Print ParsePath2("Windows\SysWOW64\AcLayers.dll", -2)
Debug.Print ParsePath2("Windows\SysWOW64\.fakedir", -2)
Debug.Print ParsePath2("Windows\SysWOW64\fakefile.ext", -2)
Debug.Print ParsePath2(".fakedir", -2)
Debug.Print ParsePath2("fakefile.txt", -2)
Debug.Print ParsePath2("fakefile.onenote", -2)
Debug.Print ParsePath2("C:\Personal\Workspace\Code\PythonVenvs\xlwings_test\.idea", -2)
Debug.Print ParsePath2("Windows", -2) ' Expected to raise error 52
End If
If True Then
Debug.Print String(2, vbCrLf)
Debug.Print "ParsePath2 ""\Windows\SysWOW64\fakefile.ext"" with different ReturnType values"
Debug.Print , "{empty}", "D", ParsePath2("Windows\SysWOW64\fakefile.ext")(1)
Debug.Print , "0", "D", ParsePath2("Windows\SysWOW64\fakefile.ext", 0)(1)
Debug.Print , "1", "ext", ParsePath2("Windows\SysWOW64\fakefile.ext", 1)
Debug.Print , "10", "file", ParsePath2("Windows\SysWOW64\fakefile.ext", 10)
Debug.Print , "11", "file.ext", ParsePath2("Windows\SysWOW64\fakefile.ext", 11)
Debug.Print , "100", "path", ParsePath2("Windows\SysWOW64\fakefile.ext", 100)
Debug.Print , "110", "path\file", ParsePath2("Windows\SysWOW64\fakefile.ext", 110)
Debug.Print , "111", "path\file.ext", ParsePath2("Windows\SysWOW64\fakefile.ext", 111)
Debug.Print , "1000", "D", ParsePath2("Windows\SysWOW64\fakefile.ext", 1000)
Debug.Print , "1100", "D:\path", ParsePath2("Windows\SysWOW64\fakefile.ext", 1100)
Debug.Print , "1110", "D:\p\file", ParsePath2("Windows\SysWOW64\fakefile.ext", 1110)
Debug.Print , "1111", "D:\p\f.ext", ParsePath2("Windows\SysWOW64\fakefile.ext", 1111)
On Error GoTo EH:
' This is expected to presetn an error:
p = "Windows\SysWOW64\fakefile.ext"
n = 1010
Debug.Print "1010", "D:\p\file.ext", ParsePath2("Windows\SysWOW64\fakefile.ext", 1010)
On Error GoTo 0
End If
Exit Sub
EH:
Debug.Print , CStr(n), "Error: "; Err.Number, Err.Description
Resume Next
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function ParsePath2(ByVal DrivePathFileExt As String _
, Optional ReturnType As Integer = 0)
' Writen by Chris Advena. You may modify and use this code provided you leave
' this credit in the code.
' Parses the input DrivePathFileExt string into individual components (drive
' letter, folders, filename and extension) and returns the portions you wish
' based on ReturnType.
' Returns either an array of strings (ReturnType = 0) or an individual string
' (all other defined ReturnType values).
'
' Parameters:
' DrivePathFileExt: The full drive letter, path, filename and extension
' ReturnType: -2 or a string up of to 4 ones with leading or lagging zeros
' (e.g., 0001)
' -2: special code for debugging use in ParsePath2Test().
' Results in printing verbose information to the Immediate window.
' 0: default: Array(driveStr, pathStr, fileStr, extStr)
' 1: extension
' 10: filename stripped of extension
' 11: filename.extension, excluding drive and folders
' 100: folders, excluding drive letter filename and extension
' 111: folders\filename.extension, excluding drive letter
' 1000: drive leter only
' 1100: drive:\folders, excluding filename and extension
' 1110: drive:\folders\filename, excluding extension
' 1010, 0101, 1001: invalid ReturnTypes. Will result raise error 380, Value
' is not valid.
Dim driveStr As String, pathStr As String
Dim fileStr As String, extStr As String
Dim drivePathStr As String
Dim pathFileExtStr As String, fileExtStr As String
Dim s As String, cnt As Integer
Dim i As Integer, slashStr As String
Dim dotLoc As Integer, slashLoc As Integer, colonLoc As Integer
Dim extLen As Integer, fileLen As Integer, pathLen As Integer
Dim errStr As String
DrivePathFileExt = Trim(DrivePathFileExt)
If DrivePathFileExt = "" Then
fileStr = ""
extStr = ""
fileExtStr = ""
pathStr = ""
pathFileExtStr = ""
drivePathStr = ""
GoTo ReturnResults
End If
' Determine if Dos(/) or UNIX(\) slash is used
slashStr = GetPathSeparator(DrivePathFileExt)
' Find location of colon, rightmost slash and dot.
' COLON: colonLoc and driveStr
colonLoc = 0
driveStr = ""
If Mid(DrivePathFileExt, 2, 1) = ":" Then
colonLoc = 2
driveStr = Left(DrivePathFileExt, 1)
End If
#If Mac Then
pathFileExtStr = DrivePathFileExt
#Else ' Windows
pathFileExtStr = ""
If Len(DrivePathFileExt) > colonLoc _
Then pathFileExtStr = Mid(DrivePathFileExt, colonLoc + 1)
#End If
' SLASH: slashLoc, fileExtStr and fileStr
' Find the rightmost path separator (Win backslash or Mac Fwdslash).
slashLoc = InStrRev(DrivePathFileExt, slashStr, -1, vbBinaryCompare)
' DOT: dotLoc and extStr
' Find rightmost dot. If that dot is not part of a relative reference,
' then set dotLoc. dotLoc is meant to apply to the dot before an extension,
' NOT relative path reference dots. REl ref dots appear as "." or ".." at
' the very leftmost of the path string.
dotLoc = InStrRev(DrivePathFileExt, ".", -1, vbTextCompare)
If Left(DrivePathFileExt, 1) = "." And dotLoc <= 2 Then dotLoc = 0
If slashLoc + 1 = dotLoc Then
dotLoc = 0
If Len(extStr) = 0 And Right(pathFileExtStr, 1) <> slashStr _
Then pathFileExtStr = pathFileExtStr & slashStr
End If
#If Not Mac Then
' In windows, filenames cannot end with a dot (".").
If dotLoc = Len(DrivePathFileExt) Then
s = "Error in FileManagementMod.ParsePath2 function. " _
& "DrivePathFileExt " & DrivePathFileExt _
& " cannot end iwth a dot ('.')."
Err.Raise 52, "FileManagementMod.ParsePath2", s
End If
#End If
' extStr
extStr = ""
If dotLoc > 0 And (dotLoc < Len(DrivePathFileExt)) _
Then extStr = Mid(DrivePathFileExt, dotLoc + 1)
' fileExtStr
fileExtStr = ""
If slashLoc > 0 _
And slashLoc < Len(DrivePathFileExt) _
And dotLoc > slashLoc Then
fileExtStr = Mid(DrivePathFileExt, slashLoc + 1)
End If
' Validate the input: DrivePathFileExt
s = ""
#If Mac Then
If InStr(1, DrivePathFileExt, ":") > 0 Then
s = "DrivePathFileExt ('" & DrivePathFileExt _
& "')has invalid format. " _
& "UNIX/Mac filenames cannot contain a colon ('.')."
End If
#End If
If Not colonLoc = 0 And slashLoc = 0 And dotLoc = 0 _
And Left(DrivePathFileExt, 1) <> slashStr _
And Left(DrivePathFileExt, 1) <> "." Then
s = "DrivePathFileExt ('" & DrivePathFileExt _
& "') has invalid format. " _
& "Good example: 'C:\folder\file.txt'"
ElseIf colonLoc <> 0 And colonLoc <> 2 Then
' We are on Windows and there is a colon; it can only be
' in position 2.
s = "DrivePathFileExt ('" & DrivePathFileExt _
& "') has invalid format. " _
& "In the Windows operating system, " _
& "a colon (':') can only be the second character '" _
& "of a valid file path. "
ElseIf Left(DrivePathFileExt, 1) = ":" _
Or InStr(3, DrivePathFileExt, ":", vbTextCompare) > 0 Then
'If path contains a drive letter, it must contain at least one slash.
s = "DrivePathFileExt ('" & DrivePathFileExt _
& "') has invalid format. " _
& "Colon can only appear in the second character position." _
& slashStr & "')."
ElseIf colonLoc > 0 And slashLoc = 0 _
And Len(DrivePathFileExt) > 2 Then
'If path contains a drive letter, it must contain at least one slash.
s = "DrivePathFileExt ('" & DrivePathFileExt _
& "') has invalid format. " _
& "The last dot ('.') cannot be before the last file separator '" _
& slashStr & "')."
ElseIf colonLoc = 2 _
And InStr(1, DrivePathFileExt, slashStr, vbTextCompare) = 0 _
And Len(DrivePathFileExt) > 2 Then
' There is a colon, but no file separator (slash). This is invalid.
s = "DrivePathFileExt ('" & DrivePathFileExt _
& "') has invalid format. " _
& "If a drive letter is included, then there must be at " _
& "least one file separator character ('" & slashStr & "')."
ElseIf Len(driveStr) > 0 And Len(DrivePathFileExt) > 2 And slashLoc = 0 Then
' If path contains a drive letter and is more than 2 character long
' (e.g., 'C:'), it must contain at least one slash.
s = "DrivePathFileExt cannot contain a drive letter but no path separator."
End If
If Len(s) > 0 Then
End If
' Determine if DrivePathFileExt = DrivePath
' or = Path (with no fileStr or extStr components).
If Right(DrivePathFileExt, 1) = slashStr _
Or slashLoc = 0 _
Or dotLoc = 0 _
Or (dotLoc > 0 And dotLoc <= slashLoc + 1) Then
' If rightmost character is the slashStr, then no fileExt exists, just drivePath
' If no dot found, then no extension. Assume a folder is after the last slashstr,
' not a filename.
' If a dot is found (extension exists),
' If a rightmost dot appears one-char to the right of the rightmost slash
' or anywhere before (left) of that, it is not a file/ext separator. Exmaple:
' 'C:\folder1\.folder2' Then
' If no slashes, then no fileExt exists. It must just be a driveletter.
' DrivePathFileExt contains no file or ext name.
fileStr = ""
extStr = ""
fileExtStr = ""
pathStr = pathFileExtStr
drivePathStr = DrivePathFileExt
GoTo ReturnResults
Else
' fileStr
fileStr = ""
If slashLoc > 0 Then
If Len(extStr) = 0 Then
fileStr = fileExtStr
Else
' length of filename excluding dot and extension.
i = Len(fileExtStr) - Len(extStr) - 1
fileStr = Left(fileExtStr, i)
End If
Else
s = "Error in FileManagementMod.ParsePath2 function. " _
& "*** Unhandled scenario: find fileStr when slashLoc = 0. *** "
Err.Raise 52, "FileManagementMod.ParsePath2", s
End If
' pathStr
pathStr = ""
' length of pathFileExtStr excluding fileExt.
i = Len(pathFileExtStr) - Len(fileExtStr)
pathStr = Left(pathFileExtStr, i)
' drivePathStr
drivePathStr = ""
' length of DrivePathFileExt excluding dot and extension.
i = Len(DrivePathFileExt) - Len(fileExtStr)
drivePathStr = Left(DrivePathFileExt, i)
End If
ReturnResults:
' ReturnType uses a 4-digit binary code: dpfe = drive path file extension,
' where 1 = return in array and 0 = do not return in array
' -2, and 0 are special cases that do not follow the code.
' Note: pathstr is determined with the tailing slashstr
If Len(drivePathStr) > 0 And Right(drivePathStr, 1) <> slashStr _
Then drivePathStr = drivePathStr & slashStr
If Len(pathStr) > 0 And Right(pathStr, 1) <> slashStr _
Then pathStr = pathStr & slashStr
#If Not Mac Then
' Including this code add a slash to the beginnning where missing.
' the downside is that it would create an absolute path where a
' sub-path of the current folder is intended.
'If colonLoc = 0 Then
' If Len(drivePathStr) > 0 And Not IsIn(Left(drivePathStr, 1), slashStr, ".") _
Then drivePathStr = slashStr & drivePathStr
' If Len(pathStr) > 0 And Not IsIn(Left(pathStr, 1), slashStr, ".") _
Then pathStr = slashStr & pathStr
' If Len(pathFileExtStr) > 0 And Not IsIn(Left(pathFileExtStr, 1), slashStr, ".") _
Then pathFileExtStr = slashStr & pathFileExtStr
'End If
#End If
Select Case ReturnType
Case -2 ' used for ParsePath2Test() only.
ParsePath2 = "DrivePathFileExt " _
& CStr(Nz(DrivePathFileExt, "{empty string}")) _
& vbCrLf & " " _
& "-------------- -----------------------------------------" _
& vbCrLf & " " & "D:\Path\ " & drivePathStr _
& vbCrLf & " " & "\path[\file.ext] " & pathFileExtStr _
& vbCrLf & " " & "\path\ " & pathStr _
& vbCrLf & " " & "file.ext " & fileExtStr _
& vbCrLf & " " & "file " & fileStr _
& vbCrLf & " " & "ext " & extStr _
& vbCrLf & " " & "D " & driveStr _
& vbCrLf & vbCrLf
' My custom debug printer prints to Immediate winodw and log file.
' Dbg.Prnt 2, ParsePath2
Debug.Print ParsePath2
Case 1 '0001: ext
ParsePath2 = extStr
Case 10 '0010: file
ParsePath2 = fileStr
Case 11 '0011: file.ext
ParsePath2 = fileExtStr
Case 100 '0100: path
ParsePath2 = pathStr
Case 110 '0110: (path, file)
ParsePath2 = pathStr & fileStr
Case 111 '0111:
ParsePath2 = pathFileExtStr
Case 1000
ParsePath2 = driveStr
Case 1100
ParsePath2 = drivePathStr
Case 1110
ParsePath2 = drivePathStr & fileStr
Case 1111
ParsePath2 = DrivePathFileExt
Case 1010, 101, 1001
s = "Error in FileManagementMod.ParsePath2 function. " _
& "Value of Paramter (ReturnType = " _
& CStr(ReturnType) & ") is not valid."
Err.Raise 380, "FileManagementMod.ParsePath2", s
Case Else ' default: 0
ParsePath2 = Array(driveStr, pathStr, fileStr, extStr)
End Select
End Function
Supporting function GetPathSeparatorTest extends the native Application.pathSeparator (or bypasses when needed) to work on Mac and Win. It can also takes an optional path string and will try to determine the path separator used in the string (favoring the OS native path separator).
Private Sub GetPathSeparatorTest()
Dim s As String
Debug.Print "GetPathSeparator(s):"
Debug.Print "s not provided: ", GetPathSeparator
s = "C:\folder1\folder2\file.ext"
Debug.Print "s = "; s, GetPathSeparator(DrivePathFileExt:=s)
s = "C:/folder1/folder2/file.ext"
Debug.Print "s = "; s, GetPathSeparator(DrivePathFileExt:=s)
End Sub
Function GetPathSeparator(Optional DrivePathFileExt As String = "") As String
' by Chris Advena
' Finds the path separator from a string, DrivePathFileExt.
' If DrivePathFileExt is not provided, return the operating system path separator
' (Windows = backslash, Mac = forwardslash).
' Mac/Win compatible.
' Initialize
Dim retStr As String: retStr = ""
Dim OSSlash As String: OSSlash = ""
Dim OSOppositeSlash As String: OSOppositeSlash = ""
Dim PathFileExtSlash As String
GetPathSeparator = ""
retStr = ""
' Determine if OS expects fwd or back slash ("/" or "\").
On Error GoTo EH
OSSlash = Application.pathSeparator
If DrivePathFileExt = "" Then
' Input parameter DrivePathFileExt is empty, so use OS file separator.
retStr = OSSlash
Else
' Input parameter DrivePathFileExt provided. See if it contains / or \.
' Set OSOppositeSlash to the opposite slash the OS expects.
OSOppositeSlash = "\"
If OSSlash = "\" Then OSOppositeSlash = "/"
' If DrivePathFileExt does NOT contain OSSlash
' and DOES contain OSOppositeSlash, return OSOppositeSlash.
' Otherwise, assume OSSlash is correct.
retStr = OSSlash
If InStr(1, DrivePathFileExt, OSSlash, vbTextCompare) = 0 _
And InStr(1, DrivePathFileExt, OSOppositeSlash, vbTextCompare) > 0 Then
retStr = OSOppositeSlash
End If
End If
GetPathSeparator = retStr
Exit Function
EH:
' Application.PathSeparator property does not exist in Access,
' so get it the slightly less easy way.
#If Mac Then ' Application.PathSeparator doesn't seem to exist in Access...
OSSlash = "/"
#Else
OSSlash = "\"
#End If
Resume Next
End Function
Supporting function (actually commented out, so you can skip this if you don't plan to use it).
Sub IsInTest()
' IsIn2 is case insensitive
Dim StrToFind As String, arr As Variant
arr = Array("Me", "You", "Dog", "Boo")
StrToFind = "doG"
Debug.Print "Is '" & CStr(StrToFind) & "' in list (expect True): " _
, IsIn(StrToFind, "Me", "You", "Dog", "Boo")
StrToFind = "Porcupine"
Debug.Print "Is '" & CStr(StrToFind) & "' in list (expect False): " _
, IsIn(StrToFind, "Me", "You", "Dog", "Boo")
End Sub
Function IsIn(ByVal StrToFind, ParamArray StringArgs() As Variant) As Boolean
' StrToFind: the string to find in the list of StringArgs()
' StringArgs: 1-dimensional array containing string values.
' Built for Strings, but actually works with other data types.
Dim arr As Variant
arr = StringArgs
IsIn = Not IsError(Application.Match(StrToFind, arr, False))
End Function
Try this function:
Function FolderPath(FilePath As String) As String
'--------------------------------------------------
'Returns the folder path form the file path.
'Written by: Christos Samaras
'Date: 06/11/2013
'--------------------------------------------------
Dim FileName As String
With WorksheetFunction
FileName = Mid(FilePath, .Find("*", .Substitute(FilePath, "\", "*", Len(FilePath) - _
Len(.Substitute(FilePath, "\", "")))) + 1, Len(FilePath))
End With
FolderPath = Left(FilePath, Len(FilePath) - Len(FileName) - 1)
End Function
If you don't want to remove the last backslash "\" at the end of the folder's path, change the last line with this:
FolderPath = Left(FilePath, Len(FilePath) - Len(FileName))
Example:
FolderPath("C:\Users\Christos\Desktop\LAT Analysers Signal Correction\1\TP 14_03_2013_5.csv")
gives:
C:\Users\Christos\Desktop\LAT Analysers Signal Correction\1
or
C:\Users\Christos\Desktop\LAT Analysers Signal Correction\1\
in the second case (note that there is a backslash at the end).
I hope it helps...
Use these codes and enjoy it.
Public Function GetDirectoryName(ByVal source As String) As String()
Dim fso, oFolder, oSubfolder, oFile, queue As Collection
Set fso = CreateObject("Scripting.FileSystemObject")
Set queue = New Collection
Dim source_file() As String
Dim i As Integer
queue.Add fso.GetFolder(source) 'obviously replace
Do While queue.Count > 0
Set oFolder = queue(1)
queue.Remove 1 'dequeue
'...insert any folder processing code here...
For Each oSubfolder In oFolder.SubFolders
queue.Add oSubfolder 'enqueue
Next oSubfolder
For Each oFile In oFolder.Files
'...insert any file processing code here...
'Debug.Print oFile
i = i + 1
ReDim Preserve source_file(i)
source_file(i) = oFile
Next oFile
Loop
GetDirectoryName = source_file
End Function
And here you can call function:
Sub test()
Dim s
For Each s In GetDirectoryName("C:\New folder")
Debug.Print s
Next
End Sub