VotingOptions Response Reply with Custom Body - vba

I have a macro that runs when I accept specific emails. It replies to the email with a voting option of either "Approved" or "Rejected".
When the recipient of the voting option responds with their choice, the received "choice" email does not have a body. Is there a way to retain the body?
I've tried this so far:
With objMsg
.To = strEmail
.HTMLBody = Item.HTMLBody
.Subject = "Is This Approved?"
.VotingOptions = "Approved;Rejected"
.VotingResponse = "Yes"
.Attachments.Add Item
.Display
.Send
End With
I believe this is only affecting the initial email and not the responses.
I looked at the MailItem Object, but didn't see any options for Voting outside of .VotingOptions and .VotingResponse.
I would be open to ideas outside of Voting buttons (such as a Task or something like that) as long as it can include the body in the response.

As I mentioned in the comments above, I think adding a small hash to the subject is the only way you can track message thread replies. Below is some code for generating a hash based on the date and time to add as a prefix to the subject
Sub TestHash()
Dim lDate As Date: lDate = Now
MsgBox DateTimeHash(lDate)
End Sub
Function DateTimeHash(lDate As Date) As String
DateTimeHash = "#" & fBase36Encode(DateValue(lDate)) & _
fBase36Encode(60 * (60 * Hour(lDate) + Minute(lDate)) + Second(lDate)) & "#"
End Function
Function fBase36Encode(ByRef lngNumToConvert As Long) As String
'Will Convert any Positive Integer to a Base36 String
fBase36Encode = "0"
If lngNumToConvert = 0 Then Exit Function
Dim strAlphabet As String: strAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
fBase36Encode = vbNullString
Do While lngNumToConvert <> 0
fBase36Encode = Mid(strAlphabet, lngNumToConvert Mod 36 + 1, 1) & fBase36Encode
lngNumToConvert = lngNumToConvert \ 36
Loop
End Function
* EDIT *
Using a reversible base 64 hash instead:
Sub TestHash()
Dim lDate As Date: lDate = Now
Debug.Print DateTimeHash(lDate)
Debug.Print DateTimeUnhash(DateTimeHash(lDate))
End Sub
Function DateTimeHash(ByRef lDate As Date) As String
Dim Secs As String: Secs = Encode64(60 * (60 * Hour(lDate) + Minute(lDate)) + Second(lDate))
DateTimeHash = "#" & Encode64(DateValue(lDate)) & String(3 - Len(Secs), "0") & Secs & "#"
End Function
Function DateTimeUnhash(ByRef Hash As String) As Date
Hash = Replace(Hash, "#", "")
Dim Days As Long: Days = Decode64(Left(Hash, Len(Hash) - 3))
Dim Secs As Long: Secs = Decode64(Right(Hash, 3))
DateTimeUnhash = DateAdd("d", Days, "0") + DateAdd("s", Secs, "0")
End Function
Function Encode64(ByRef Value As Long) As String
'Will Convert any Positive Integer to a Base64 String
Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
Encode64 = IIf(Value > 0, vbNullString, "0")
If Encode64 = "0" Then Exit Function
Do While Value <> 0
Encode64 = Mid(Base64, Value Mod 64 + 1, 1) & Encode64
Value = Value \ 64
Loop
End Function
Function Decode64(ByRef Value As String) As Long
'Will Convert any Base64 String to a Positive Integer
Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
Decode64 = IIf(Value = "", -1, 0)
Dim i As Long: For i = 1 To Len(Value)
If Decode64 = -1 Then Exit Function ' Error detected with Value string
Decode64 = Decode64 * 64
Decode64 = IIf(InStr(Base64, Mid(Value, i, 1)) > 0, _
Decode64 + InStr(Base64, Mid(Value, i, 1)) - 1, -1)
Next i
End Function

Related

VBA: Isolate a decimal number from a string so that I can add or subtract from it

I'm attempting to write a program that involves finding strings with numerical values that are +1 and -1 from the numerical value located within another string. (The rest of the program is fine, it's just this section that I'm having a difficult time with).
For example:
If I have the parent string: name[CE18.2]-abritraryinfo
I need to find a way to isolate that 18.2 so that I can add 1 to it and subtract 1 from it to create two new numerical values of 19.2 and 17.2
I need to be able to do this in such a way that I can find this number in strings whose 'name' section and whose number after CE vary according to the different parent strings.
What I've tried already is this:
'''
Result = Empty 'Resets the value of the result after it changes to the next name
f = InStr(c, "CE") 'Finds at which position in the string CE is located. The position is the C of CE
z = Mid(c, f, 8) 'Pulls 8 units from the string starting at the position dictated by f
stringLength = Len(z) 'Gives the Length of the section pulled by Z
For i = 1 To stringLength 'From the first position to the final position
If IsNumeric(Mid(z, i, 1)) Then
Result = Result & Mid(z, i, 1) 'Gives the numbers in the string section pulled by Z
End If
Next i
'''
but it doesn't work as it ignores the decimal point.
Any advice would be incredibly helpful! Thanks in advance!
One of the simple solution is:
Sub test1()
inputS = "name[CE18.2]-abritraryinfo"
pos = InStr(inputS, "[CE")
If pos > 0 Then
x = Val(Mid(inputS, pos + 3))
Debug.Print x, x - 1, x + 1
End If
End Sub
Output:
18,2 17,2 19,2
String Between Two Strings
Option Explicit
Sub gsbtsTEST()
Const lStr As String = "CE"
Const rStr As String = "]"
Const sString As String = "name[CE18.2]-abritraryinfo"
Dim ResString As String
ResString = GetStringBetweenTwoStrings(sString, lStr, rStr)
Dim ResValue As Double
If IsNumeric(ResString) Then
ResValue = Val(ResString)
End If
Debug.Print ResString, ResValue - 1, ResValue, ResValue + 1
End Sub
Function GetStringBetweenTwoStrings( _
ByVal sString As String, _
ByVal lStr As String, _
ByVal rStr As String, _
Optional ByVal CompareMethod As VbCompareMethod = vbTextCompare) _
As String
Dim lPos As Long: lPos = InStr(1, sString, lStr, CompareMethod)
If lPos = 0 Then Exit Function
Dim rPos As Long: rPos = InStr(1, sString, rStr, CompareMethod)
If rPos = 0 Then Exit Function
lPos = lPos + Len(lStr)
If lPos < rPos Then
GetStringBetweenTwoStrings = Mid(sString, lPos, rPos - lPos)
End If
End Function

Applying a UDF to each value used as a range for sumproduct

I have written a UDF in VBA that takes a parameter and a string and processes them to return a double. I would like to be able to use this formula to process a column of a table for a range in a sumproduct formula and I'm having some issues.
Public Function ColorCount(Color As String, ToCount As String)
Dim WordArray() As String
ToCount = Replace(ToCount, " ", "")
WordArray() = Split(ToCount, "}{")
ColorCount = 0
For i = LBound(WordArray) To UBound(WordArray)
WordArray(i) = Replace(WordArray(i), "{", "")
WordArray(i) = Replace(WordArray(i), "}", "")
If UCase(Color) = UCase(WordArray(i)) Then
ColorCount = ColorCount + 1
ElseIf UCase(WordArray(i)) Like UCase(Color) & "[/\]*" Or UCase(WordArray(i)) Like "*[/\]" & UCase(Color) Then
ColorCount = ColorCount + 0.5
End If
Next i
End Function
I have data in a table that I would like to be able to call for a sum product. I've tried something similar to =sumproduct(Table[Quant],ColorCount("Color", Table[Colors]) but it doesn't seem to work.
Any advice or help would be appreciated!
Write all the processing into the UDF. It seems a shame not to take advantage of the superior (compared to SUMPRODUCT) looping available in VBA.
Option Explicit
Public Function udfColorCount(theColor As String, toCount As Range, toQty As Range)
Dim c As Integer, i As Integer, colorString As String, colorArray As Variant
'toCount = Replace(toCount, " ", vbNullString)
udfColorCount = 0
For c = 1 To toQty.Cells.Count
Debug.Print toCount.Cells(c).Value2
colorString = Replace(toCount.Cells(c).Value2, Chr(32), vbNullString)
colorArray = Split(Mid(colorString, 2, Len(colorString) - 2), "}{")
For i = LBound(colorArray) To UBound(colorArray)
If UCase(theColor) = UCase(colorArray(i)) Then
udfColorCount = udfColorCount + toQty.Cells(c)
ElseIf CBool(InStr(1, colorArray(i), theColor, vbTextCompare)) Then
udfColorCount = udfColorCount + 0.5 * toQty.Cells(c)
End If
Next i
Next c
End Function

VBA to load very large file in one go (no buffering)

I am experiencing an unexpected vb limitation on the string max size, as explained in this post:
VBA unexpected reach of string size limit
While I was expecting to be able to load files up to 2GB (2^31 char) using open path for binary and get function, I get an out of string space error when I try to load a string larger than 255,918,061 characters.
I managed to work around this issue buffering the input stream of get. The problem is that I need to load the file as an array of string by splitting the buffer on vbCrLf characters.
This requires then to build the array line by line. Moreover, since I cannot be sure whether the buffer is ending on a break line or not I need additional operations. This solution is Time and Memory consuming. Loading a file of 300MB with this code costs 900MB (!) use of memory by excel. Is there a better solution ?
Here bellow is my code:
Function Load_File(path As String) As Variant
Dim MyData As String, FNum As Integer
Dim LenRemainingBytes As Long
Dim BufferSizeCurrent As Long
Dim FileByLines() As String
Dim CuttedLine As Boolean
Dim tmpSplit() As String
Dim FinalSplit() As String
Dim NbOfLines As Long
Dim LastLine As String
Dim count As Long, i As Long
Const BufferSizeMax As Long = 100000
FNum = FreeFile()
Open path For Binary As #FNum
LenRemainingBytes = LOF(FNum)
NbOfLines = FileNbOfLines(path)
ReDim FinalSplit(NbOfLines)
CuttedLine = False
Do While LenRemainingBytes > 0
MyData = ""
If LenRemainingBytes > BufferSizeMax Then
BufferSizeCurrent = BufferSizeMax
Else
BufferSizeCurrent = LenRemainingBytes
End If
MyData = Space$(BufferSizeCurrent)
Get #FNum, , MyData
tmpSplit = Split(MyData, vbCrLf)
If CuttedLine Then
count = count - 1
tmpSplit(0) = LastLine & tmpSplit(0)
For i = 0 To UBound(tmpSplit)
If count > NbOfLines Then Exit For
FinalSplit(count) = tmpSplit(i)
count = count + 1
Next i
Else
For i = 0 To UBound(tmpSplit)
If count > NbOfLines Then Exit For
FinalSplit(count) = tmpSplit(i)
count = count + 1
Next i
End If
Erase tmpSplit
LastLine = Right(MyData, Len(MyData) - InStrRev(MyData, vbCrLf) - 1)
CuttedLine = Len(LastLine) > 1
LenRemainingBytes = LenRemainingBytes - BufferSizeCurrent
Loop
Close FNum
Load_File = FinalSplit
Erase FinalSplit
End Function
Where the function FileNbOfLines is efficiently returning the number of line break characters.
Edit:
My Needs are:
To look for a specific string within the file
To get a specific number of lines coming after this string
Here you go, not pretty but should give you the general concept:
Sub GetLines()
Const fileName As String = "C:\Users\bloggsj\desktop\testfile.txt"
Const wordToFind As String = "FindMe"
Dim lineStart As String
Dim lineCount As String
Dim linesAfterWord As Long
With CreateObject("WScript.Shell")
lineCount = .Exec("CMD /C FIND /V /C """" """ & fileName & """").StdOut.ReadAll
lineStart = Split(.Exec("CMD /C FIND /N """ & wordToFind & """ """ & fileName & """").StdOut.ReadAll, vbCrLf)(2)
End With
linesAfterWord = CLng(Trim(Mid(lineCount, InStrRev(lineCount, ":") + 1))) - CLng(Trim(Mid(lineStart, 2, InStr(lineStart, "]") - 2)))
Debug.Print linesAfterWord
End Sub
Uses CMD to count the number of lines, then find the line at which the word appears, then subtract one from the other to give you the amount of lines after the word has been found.
Answer: Yes, using ReadAll from FSO should do the job.
Best answer: Just avoid it !
My needs were:
Identify a specific string within the file
Extract a certain number of lines after this string
As far as you precisely know the exact amout of data you want to extract, and assuming this amount of data is below vba string size limit (!), here is what it does the job the faster.
Decrease of computation time is improved using binary comparison of strings. My code is as follows:
Function GetFileLines(path As String, str As String, NbOfLines As Long) As String()
Const BUFSIZE As Long = 100000
Dim StringFound As Boolean
Dim lfAnsi As String
Dim strAnsi As String
Dim F As Integer
Dim BytesLeft As Long
Dim Buffer() As Byte
Dim strBuffer As String
Dim BufferOverlap As String
Dim PrevPos As Long
Dim NextPos As Long
Dim LineCount As Long
Dim data As String
F = FreeFile(0)
strAnsi = StrConv(str, vbFromUnicode) 'Looked String
lfAnsi = StrConv(vbLf, vbFromUnicode) 'LineBreak character
Open path For Binary Access Read As #F
BytesLeft = LOF(F)
ReDim Buffer(BUFSIZE - 1)
'Overlapping buffer is 3/2 times the size of strBuffer
'(two bytes per character)
BufferOverlap = Space$(Int(3 * BUFSIZE / 4))
StringFound = False
Do Until BytesLeft = 0
If BytesLeft < BUFSIZE Then ReDim Buffer(BytesLeft - 1)
Get #F, , Buffer
strBuffer = Buffer 'Binary copy of bytes.
BytesLeft = BytesLeft - LenB(strBuffer)
Mid$(BufferOverlap, Int(BUFSIZE / 4) + 1) = strBuffer 'Overlapping Buffer
If Not StringFound Then 'Looking for the the string
PrevPos = InStrB(BufferOverlap, strAnsi) 'Position of the looked string within the buffer
StringFound = PrevPos <> 0
If StringFound Then strBuffer = BufferOverlap
End If
If StringFound Then 'When string is found, loop until NbOfLines
Do Until LineCount = NbOfLines
NextPos = InStrB(PrevPos, strBuffer, lfAnsi)
If NextPos = 0 And LineCount < NbOfLines Then 'Buffer end reached, NbOfLines not reached
'Adding end of buffer to data
data = data & Mid$(StrConv(strBuffer, vbUnicode), PrevPos)
PrevPos = 1
Exit Do
Else
'Adding New Line to data
data = data & Mid$(StrConv(strBuffer, vbUnicode), PrevPos, NextPos - PrevPos + 1)
End If
PrevPos = NextPos + 1
LineCount = LineCount + 1
If LineCount = NbOfLines Then Exit Do
Loop
End If
If LineCount = NbOfLines then Exit Do
Mid$(BufferOverlap, 1, Int(BUFSIZE / 4)) = Mid$(strBuffer, Int(BUFSIZE / 4))
Loop
Close F
GetFileLines = Split(data, vbCrLf)
End Function
To crunch even more computation time, it is highly advised to use fast string concatenation as explained here.
For instance the following function can be used:
Sub FastConcat(ByRef Dest As String, ByVal Source As String, ByRef ccOffset)
Dim L As Long, Buffer As Long
Buffer = 50000
L = Len(Source)
If (ccOffset + L) >= Len(Dest) Then
If L > Buffer Then
Dest = Dest & Space$(L)
Else
Dest = Dest & Space$(Buffer)
End If
End If
Mid$(Dest, ccOffset + 1, L) = Source
ccOffset = ccOffset + L
End Sub
And then use the function as follows:
NbOfChars = 0
Do until...
FastConcat MyString, AddedString, NbOfChars
Loop
MyString = Left$(MyString,NbOfChars)

How do I get an Ascii to warp to a certain value after it has past 122?

I am trying to write an encryption program. The problem I am facing is that I am converting the text to ascii and then adding on the offset. However when it goes past the letter 'z' I want it to warp back to 'a' and go from there.
Sub enc()
Text = TextBox1.Text
finalmessage = ""
letters = Text.ToCharArray
offset = ComboBox1.SelectedItem
For x = LBound(letters) To UBound(letters)
finalmessage = finalmessage + Chr(Asc(letters(x)) + offset)
Next
TextBox2.Text = finalmessage
End Sub
I guess to make it easy to decode afterwards, you should to it somewhat in the line of base64 encoding, first encoding everything to a normalized binary string, then encode in the range you want (since using binary, it has to be something that fits with 2^X).
To match your range, i used a baseset of 32, and a simple encoding decoding example (a bit more verbose that it should be, perhaps)
Module Module1
Dim encodeChars As String = "abcdefghijklmnopqrstuvwxyzABCDEF" ' use 32 as a base
Function Encode(text As String) As String
Dim bitEncoded As String = ""
Dim outputMessage As String = ""
For Each ch As Char In text.ToCharArray()
Dim i As Integer = Convert.ToByte(ch)
bitEncoded &= Convert.ToString(i, 2).PadLeft(8, "0"c)
Next
While bitEncoded.Length Mod 5 <> 0
bitEncoded &= "0"
End While
For position As Integer = 0 To bitEncoded.Length - 1 Step 5
Dim range As String = bitEncoded.Substring(position, 5)
Dim index As Integer = Convert.ToInt32(range, 2)
outputMessage &= encodeChars(index).ToString()
Next
Return outputMessage
End Function
Function Decode(encodedText As String) As String
Dim bitEncoded As String = ""
Dim outputMessage As String = ""
For Each ch In encodedText
Dim index As Integer = encodeChars.IndexOf(ch)
If index < 0 Then
Throw New FormatException("Invalid character in encodedText!")
End If
bitEncoded &= Convert.ToString(index, 2).PadLeft(5, "0"c)
Next
' strip the extra 0's
While bitEncoded.Length Mod 8 <> 0
bitEncoded = bitEncoded.Substring(0, bitEncoded.Length - 1)
End While
For position As Integer = 0 To bitEncoded.Length - 1 Step 8
Dim range As String = bitEncoded.Substring(position, 8)
Dim index As Integer = Convert.ToInt32(range, 2)
outputMessage &= Chr(index).ToString()
Next
Return outputMessage
End Function
Sub Main()
Dim textToEncode As String = "This is a small test, with some special characters! Just testing..."
Dim encodedText As String = Encode(textToEncode)
Dim decodedText As String = Decode(encodedText)
Console.WriteLine(textToEncode)
Console.WriteLine(encodedText)
Console.WriteLine(decodedText)
If Not String.Equals(decodedText, textToEncode) Then
Console.WriteLine("Encoding / decoding failed!")
Else
Console.WriteLine("Encoding / decoding completed succesfully!")
End If
Console.ReadLine()
End Sub
End Module
this then gives the following output?
This is a small test, with some special characters! Just testing...
krugsCzanfzsayjaonwwcBdmebAgkCBufqqhoAlunaqhgBBnmuqhgCdfmnuwcBbamnugcCtbmnAgkCtteeqeuDltoqqhizltoruwCzzofyxa
This is a small test, with some special characters! Just testing...
Encoding / decoding completed succesfully!

To find the memory usage of a particular process

I am developing an application in visual basic 2010, that finds the memory usage of a particular process. I came across this code:
Option Explicit
Private Sub Command1_Click()
Debug.Print GetProcessMemory("vb6.exe")
End Sub
Private Function GetProcessMemory(ByVal app_name As String) As String
Dim Process As Object
Dim dMemory As Double
For Each Process In GetObject("winmgmts:").ExecQuery("Select WorkingSetSize from Win32_Process Where Name = '" & app_name & "'")
dMemory = Process.WorkingSetSize
Next
If dMemory > 0 Then
GetProcessMemory = ResizeKb(dMemory)
Else
GetProcessMemory = "0 Bytes"
End If
End Function
Private Function ResizeKb(ByVal b As Double) As String
Dim bSize(8) As String, i As Integer
bSize(0) = "Bytes"
bSize(1) = "KB" 'Kilobytes
bSize(2) = "MB" 'Megabytes
bSize(3) = "GB" 'Gigabytes
bSize(4) = "TB" 'Terabytes
bSize(5) = "PB" 'Petabytes
bSize(6) = "EB" 'Exabytes
bSize(7) = "ZB" 'Zettabytes
bSize(8) = "YB" 'Yottabytes
For i = UBound(bSize) To 0 Step -1
If b >= (1024 ^ i) Then
ResizeKb = ThreeNonZeroDigits(b / (1024 ^ _
i)) & " " & bSize(i)
Exit For
End If
Next
End Function
Private Function ThreeNonZeroDigits(ByVal value As Double) As Double
If value >= 100 Then
ThreeNonZeroDigits = FormatNumber(value)
ElseIf value >= 10 Then
ThreeNonZeroDigits = FormatNumber(value, 1)
Else
ThreeNonZeroDigits = FormatNumber(value, 2)
End If
End Function
but this does not work in vb2010. It returns 0bytes. Please help. Alternative techniques are also appreciated.