Outlook VBA macro to create txt file - vba

I am not very clued up with VBA and need some help with the following:
I want to create a txt file and save it in a specific location (overwriting the existing file with the same name).
The text file must countain the last word in the email subject, which will be an Account Number. For the purpose of this explanation I'm going to call the account number Variable1.
if the Email Subject = Account Recon - 10201314050019434586
then Variable1 = 10201314050019434586
The text file that must be created/replaced:
C:\Users\tenba1\Documents\QlikView\Account Recons\Recon_Acct.txt
The text in the file must be:
SET vAcct = 'Variable1';
So in this example the text in the file must be:
SET vAcct = '10201314050019434586';
The macro must look at the last word in the subject (i.e. everything after the last space) - an account number is not always 20 digits.
Thanks in advance

This should do the trick. It's a macro script which takes the Subject of the top opened email and writes the Accountnumber into Recon_Acct.txt. If there is no number, the text will be SET vAcct = ''. The File will be overwritten, every time you execute the script.
Sub writeSubjectToFile()
Const FILEPATH = "C:\Users\tenba1\Documents\QlikView\Account Recons\Recon_Acct.txt"
Dim objEmailItem As Object, strSubject
Dim strSubject As String
Dim strText As String
Set objEmailItem = Application.ActiveInspector.CurrentItem
strSubject = objEmailItem.subject
strText = Trim(Right(strSubject, Len(strSubject) - InStr(1, strSubject, "-")))
Open FILEPATH For Output As 1
Print #1, "SET vAcct = '" & strText & "';"
Close #1
End Sub

Related

Access DB - Save file location (full) link/path in one field and file name in other field (both or either should be clickable to open the file)

This is my first post and I am new to MS Access with no coding experience. I have a Access Database with a table call Attachments with field as "Saved Path" & "File Name". Both are Hyperlink data type.
Since there is limit of 2GB under MS Access attachments option, I would like to save a link to the file saved outside of MS Access.
What I am looking for is, when a user click on Saved Path it opens a windows dialogue box and user select the file (already created and saved under a network path) and this link/path including file name gets saved under this column. So when required they can refer back to that record and just click on the like to open that file directly from there. And under File Name field the name of the file is copied and saved.
Private Sub Saved_Path_Click()
Dim f As Object
Dim strFullpath As String
Dim strFolder As String
Dim strFile As String
Dim intPos As Integer
Dim varItem As Variant
Set f = Application.FileDialog(3)
f.AllowMultiSelect = False
strFullpath = BrowseFile
If f.Show Then
intPos = InStrRev(strFullpath, "\")
Me.Saved_Path = "#" & f.SelectedItems(1) & "#"
Application.FollowHyperlink Me.Saved_Path
End If
Set f = Nothing
End Sub
That code is not saving hyperlink string to Hyperlink type field. Access Hyperlink field data is a string composed of 3 parts separated by # character.
display text # file path\name # any reference within the file
Could have only one field (Hyperlink or Text) with full path\filename and use code to split the string when you want path or filename part.
If you use a Hyperlink type field, save full file path name formatted as a hyperlink string so it is a clickable hyperlink to open file.
Me.fieldname = "#" & f.SelectedItems(1) & "#"
Can extract path part from f.SelectedItems(1) and save to its own Hyperlink field if you want.
If you save path parts, without # characters, to separate normal text fields, options:
VBA FollowHyperlink intrinsic function in Click event procedure to open file
or
calculate a clickable hyperlink string in textbox ControlSource (no VBA).
="Click to open folder#" & [Saved_Path] & "#"
="Click to Open file#" & [Saved_Path] & "\" & [File_Name] & "#"
Set textbox IsHyperlink property to Yes so text looks like a link.

Saving attachments results in memory errors

I need to search through 9,000 emails and save the attachments with a certain timestamp (these are security camera feeds).
The code works on a small number of emails, but after about 20 the processing in Outlook appears to speed up significantly (attachments stop saving) and then Outlook hangs with a memory error.
My guess is the save step is not completed before the script moves to the next email in the target folder and therefore the backlog of saves becomes too large for Outlook.
' this function grabs the timestamp from the email body
' to use as the file rename on save in the following public sub
Private Function GetName(olItem As MailItem) As String
Const strFind As String = "Exact Submission Timestamp: "
Dim olInsp As Inspector
Dim wdDoc As Object
Dim oRng As Object
Dim strDate As String
With olItem
Set olInsp = .GetInspector
Set wdDoc = olInsp.WordEditor
Set oRng = wdDoc.Range
With oRng.Find
Do While .Execute(strFind)
oRng.Collapse 0
oRng.End = oRng.End + 23
strDate = oRng.Text
strDate = Replace(strDate, Chr(58), Chr(95))
GetName = strDate & ".jpg"
Exit Do
Loop
End With
End With
lbl_Exit:
Set olInsp = Nothing
Set wdDoc = Nothing
Set oRng = Nothing
Exit Function
End Function
Public Sub SaveAttachmentsToDisk24(MItem As outlook.MailItem)
Dim oAttachment As outlook.Attachment
Dim sSaveFolder As String
Dim strFname As String
sSaveFolder = "C:\Users\xxxxx\"
For Each oAttachment In MItem.Attachments
If oAttachment.FileName Like "*.jpg" Then
strFname = GetName(MItem)
oAttachment.SaveAsFile sSaveFolder & strFname
Set oAttachment = Nothing
Set MItem = Nothing
End If
Next oAttachment
There are other possibilities but my belief is that the memory errors are the result of creating Word objects and then not closing them. Om3r asked for more information but you ignored his requests making it impossible to provide a definitive answer. However, I wanted to prove it was possible to extract attachments from a large number of emails without problems so I have made some guesses.
I understand why you need a routine that will scan your Inbox for the backlog of 8,000 camera feed emails. I do not understand why you want to use an event to monitor your Inbox as well. I cannot believe this is a time critical task. Why not just run the scan once or twice a day? However, the routine I have coded could be adapted to create a macro to be called by an event routine. My current code relies of global variables which you will have to change to local variables. I am not a fan of global variables but I did not want to create a folder reference for every call of the inner routine and the parameter list for a macro that might be called by an event routine is fixed.
To test the code I planned to create, I first generated 790 emails to myself that matched (I hope) your camera feed emails. I had planned to create more but I think my ISP has classified me as a spammer, or perhaps a flamer, and it would not let me send any more. The body of these emails looked like:
xxx Preamble xxx ‹cr›‹lf›|
Exact Submission Timestamp: 2019-02-22 15:00:00 ‹cr›‹lf›|
xxx Postamble xxx ‹cr›‹lf›|
Your code requires the string “Exact Submission Timestamp:” followed by a date which you use as a file name. I have assumed that date in in a format that VBA can recognise as a date and I have assumed the date is ended by a standard Windows newline (carriage return, line feed). The second assumption would be easy to change. I have a routine that will accept many more date formats than VBA’s CDate which I can provide if necessary.
Each email has a different date and time between November, 2018 and February, 2019.
I would never save 8,000 files in a single disc folder. Even with a few hundred files in a folder, it becomes difficult to find the one you want. My root folder is “C:\DataArea\Test” but you can easily change that. Given the timestamp in my example email, my routine would check for folder “C:\DataArea\Test\2019” then “C:\DataArea\Test\2019\02” and finally “C:\DataArea\Test\2019\02\22”. If a folder did not exist, it would be created. The attachment is then saved in the inner folder. My code could easily be adapted to save files at the month level or the hour level depending on how many of these files you get per month, day or hour.
My routine checks every email in Inbox for the string “Exact Submission Timestamp:” followed by a date. If it finds those, it checks for an attachment with an extension of JPG. If the email passes all these tests, the attachment is saved in the appropriate disc folder and the email is moved from Outlook folder “Inbox” to “CameraFeeds1”. The reasons for moving the email are: (1) it clears the Inbox and (2) you can rerun the routine as often as you wish without finding an already processed email. I named the destination folder “CameraFeeds1” because you wrote that you wanted to do some more work on these emails. I thought you could move the emails to folder “CameraFeeds2” once you had completed this further work.
I assumed processing 790 or 8,000 emails would take a long time. In my test, the duration was not as bad as I expected; 790 emails took about one and a half minutes. However, I created a user form to show progress. I cannot include the form in my answer so you will have to create your own. Mine looks like:
The appearance is not important. What is important is the name of the form and the four controls on the form:
Form name: frmSaveCameraFeeds
TextBox name: txtCountCrnt
TextBox name: txtCountMax
CommandButton name: cmdStart
CommandButton name: cmdStop
If you run the macro StartSaveCameraFeeds it will load this form. Click [Start] to start the save process. You can let the macro run until it has checked every email in the Inbox or you can click [Stop] at any time. The stop button is not as important as I feared. I thought the routine might take hours but that was not the case.
You don’t report where your 8,000 emails are. I have an Inbox per account plus the default Inbox which I only use for testing. I moved the 790 test emails to the default Inbox and used GetDefaultFolder to reference it. I assume you know how to reference another folder if necessary. Note I use Session instead of a name space. These two methods are supposed to be equivalent but I always use Session because it is simpler and because I once had a failure with a name space that I could not diagnose. I reference folder “CameraFeeds1” relative to the Inbox.
You will have to adjust my code at least partially. For the minimum changes, do the following:
Create a new module and copy this code into it:
Option Explicit
Public Const Marker As String = "Exact Submission Timestamp: "
Public Const RootSave As String = "C:\DataArea\Test"
Public FldrIn As Outlook.Folder
Public FldrOut As Outlook.Folder
Sub StartSaveCameraFeeds()
' Reference outlook folders then pass control to frmSaveCameraFeeds
Set FldrIn = Session.GetDefaultFolder(olFolderInbox)
Set FldrOut = FldrIn.Parent.Folders("CameraFeeds1")
Load frmSaveCameraFeeds
With frmSaveCameraFeeds
.Caption = "Saving jpg files from Camera feed emails"
.txtCountCrnt = 0
.txtCountMax = FldrIn.Items.Count
.Show vbModal
End With
' Form unloaded by cmdStop within form
Set FldrIn = Nothing
Set FldrOut = Nothing
End Sub
Public Sub SaveCameraFeed(ByRef ItemCrnt As MailItem)
' Checks a single mail item to be a "camera feed" email. If the mail item is
' a "camera feed" email, it saves the JPG file using the date within the
' email body as the file name. If the mail item is not a "camera feed"
' email, it does nothing.
' To be a camera feed mail item:
' * The text body must contain a string of the form: "xxxyyyy" & vbCr & vbLf
' where "xxx" matches the public constant Marker and "yyy" is recognised
' by VBA as a date
' * It must have an attachment with an extension of "JPG" or "jpg".
' If the mail item is a camera feed email:
' * In "yyy" any colons are replaced by understores.
' * The JPG attachment is saved with the name yyy & ".jpg"
Dim DateCrnt As Date
Dim DateStr As String
Dim DayCrnt As String
Dim InxA As Long
Dim MonthCrnt As String
Dim PathFileName As String
Dim PosEnd As Long
Dim PosStart As Long
Dim SomethingToSave As Boolean
Dim YearCrnt As String
SomethingToSave = False ' Assume no JPG to save until find otherwise
With ItemCrnt
PosStart = InStr(1, .Body, Marker)
If PosStart > 0 Then
PosStart = PosStart + Len(Marker)
PosEnd = InStr(PosStart, .Body, vbCr & vbLf)
DateStr = Mid$(.Body, PosStart, PosEnd - PosStart)
If IsDate(DateStr) Then
DateCrnt = DateStr
For InxA = 1 To .Attachments.Count
If LCase(Right$(.Attachments(InxA).Filename, 4)) = ".jpg" Then
SomethingToSave = True
Exit For
End If
Next
End If
End If
If SomethingToSave Then
DateStr = Replace(DateStr, ":", "_")
YearCrnt = Year(DateCrnt)
MonthCrnt = Month(DateCrnt)
DayCrnt = Day(DateCrnt)
Call CreateDiscFldrIfItDoesntExist(RootSave, YearCrnt, MonthCrnt, DayCrnt)
PathFileName = RootSave & "\" & YearCrnt & "\" & MonthCrnt & "\" & DayCrnt & _
"\" & Trim(DateStr) & ".jpg"
.Attachments(InxA).SaveAsFile PathFileName
.Move FldrOut
End If
End With
End Sub
Public Sub CreateDiscFldrIfItDoesntExist(ByVal Root As String, _
ParamArray SubFldrs() As Variant)
' If a specified disk folder (not an Outlook folder) does not exist, create it.
' Root A disk folder which must exist and for which the user
' must have write permission.
' SubFldrs A list of sub-folders required within folder Root.
' Example call: CreateDiscFldrsIfNecessary("C:\DataArea", "Aaa", "Bbb", "Ccc")
' Result: Folder "C:\DataArea\Aaa\Bbb\Ccc" will be created if it does not already exist.
' Note: MkDir("C:\DataArea\Aaa\Bbb\Ccc") fails unless folder "C:\DataArea\Aaa\Bbb" exists.
Dim Filename As String
Dim Fldrname As String
Dim InxSF As Long
Fldrname = Root
For InxSF = LBound(SubFldrs) To UBound(SubFldrs)
Fldrname = Fldrname & "\" & SubFldrs(InxSF)
If Not PathExists(Fldrname) Then
Call MkDir(Fldrname)
End If
Next
End Sub
Public Function PathExists(ByVal Pathname As String) As Boolean
' Returns True if path exists
' Coded by Tony Dallimore
' Based on code written by iDevlop: http://stackoverflow.com/a/28237845/973283
On Error Resume Next
PathExists = ((GetAttr(Pathname) And vbDirectory) = vbDirectory)
On Error GoTo 0
End Function
I must warn you that I have modules full of standard routines that I use all the time. I believe I have included all the standard routines used by the code I have written for you. If the code fails because a sub or function is missing, post a comment and I will apologise and add the missing macro to my code.
Near the top of the above code is Public Const RootSave As String = "C:\DataArea\Test". You will have to change this to reference your root folder.
The first statement of Sub StartSaveCameraFeeds() is Set FldrIn = Session.GetDefaultFolder(olFolderInbox). Amend this as necessary if the emails are not in the default Inbox.
In the body of Sub StartSaveCameraFeeds() you will find PosEnd = InStr(PosStart, .Body, vbCr & vbLf). If the date string is not ended by a standard Windows’ newline, amend this statement as necessary.
Create a user form. Add two TextBoxes and two CommandButtons. Name them as defined above. Copy the code below to the code area of the form:
Option Explicit
Private Sub cmdStart_Click()
' Call SaveCameraFeed for every MailItem in FldrIn
Dim CountMax As Long
Dim InxI As Long
Dim MailItemCrnt As MailItem
With FldrIn
CountMax = FldrIn.Items.Count
For InxI = CountMax To 1 Step -1
If .Items(InxI).Class = olMail Then
Set MailItemCrnt = .Items(InxI)
Call SaveCameraFeed(MailItemCrnt)
Set MailItemCrnt = Nothing
End If
txtCountCrnt = CountMax - InxI + 1
DoEvents
Next
End With
Unload Me
End Sub
Private Sub cmdStop_Click()
Unload Me
End Sub
The form code should not need amendment.
As I have already written, this code processed 790 camera feed emails in about one and a half minutes. I coded a further routine that checked that for every email the date matched the name of a jpg file. I could include this routine in my answer if you would like to perform the same check.

VBA Parse Email Header Routing (Hops) Data

I'm interested in using VBA/VBscript to parse email header data. There are other answers on this site that address this to some degree, however not to the extent that I need.
I have a number of message headers that I've extracted from emails and have saved as text files, all in one folder (see image below). I would like to loop through the folder and parse the area identified in the box (perhaps save to excel or a table in Access?). The data identified in the box shows all the email "hops" (when an email is sent it is transferred between many computers - each transfer is a "hop"). This data is found in the "Received: from" section highlighted below:
*NOTE: Apologies, I'm not at a reputation as of yet to post images:
https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/76/18/3782.HSG-8-18-11-1.jpg
The result should look like this:
https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/76/18/7624.hsg-8-19-11-1.png
This is accomplished by parsing the FROM, BY, WITH, and DATESTAMP information from the above boxed area of the message header.
Kind of a tall order, I know. But I can't seem to find anything online. Any assistance (or direction to other solutions) would be appreciated.
Thank you.
Try this code and let me know if it works for you or not.
You just need to provide the path of the folder containing all your text files. The code would store the output in a file"1.txt" within the same folder. All the values in the file will be separated by double-pipe(||). Ofcourse, you can change it later as per your requirement.
Dim objFso, strFolderPath, objFolder, file, workFile, tempArr, strAllData
Dim strRecordData, arrRecordData
strFolderPath = "C:\Users\gu.singh\Desktop\Desktop\Gurman\2017\5. May\aa" 'REPLACE THIS PATH WITH YOUR FOLDER PATH
Set objFso = CreateObject("Scripting.FileSystemObject")
If objFso.FolderExists(strFolderPath) Then
Set objFolder = objFso.GetFolder(strFolderPath)
For Each file In objFolder.Files
tempArr = Split(file.Name,".")
If StrComp(tempArr(UBound(tempArr)),"txt",1)=0 Then
Set workFile = file.OpenAsTextStream(1)
strAllData = workFile.ReadAll()
workFile.Close
arrRecordData = Split(strAllData,"Received:")
For i=1 To UBound(arrRecordData) Step 1
intby = InStr(1,arrRecordData(i),"by ",1)
intfrom = InStr(1,arrRecordData(i),"from ",1)
intwith = InStr(1,arrRecordData(i),"with ",1)
intsemi = InStr(1,arrRecordData(i),";",1)
intdash = InStr(1,arrRecordData(i),"-",1)
strFROM = Trim(Mid(arrRecordData(i),intfrom+Len("from "), intby-intfrom-Len("from ")))
strBY = Trim(Mid(arrRecordData(i),intby+Len("by "), intwith-intby-Len("by ")))
strWITH = Trim(Mid(arrRecordData(i),intwith+Len("with "), intsemi-intwith-Len("with ")))
strDATE = Trim(Mid(arrRecordData(i),intsemi+Len(";"), intdash-intsemi-Len(";")))
strResult = strResult & strBY &"||"&strFROM&"||"&strWITH&"||"&strDATE&vbCrLf
Next
Set WorkFile = Nothing
End If
Next
Set objFolder = Nothing
End If
Set fyl = objFso.OpenTextFile(strFolderPath&"\1.txt",2,True)
fyl.Write strResult
fyl.Close
Set fyl=Nothing
Set objFso = Nothing

Excel VBA: Make InStr find whole string, not part of it

I'm trying to make a login/register system.
This is the registration UserForm.
Private Sub regReg_Click()
Dim TextFile As Integer
Dim FilePath As String
If regAParole.Text = "aparole" Then
FilePath = ThisWorkbook.Path & "\accounts.txt"
TextFile = FreeFile
Open FilePath For Append As #1
Print #TextFile, regID; regAmats; regParole
Close TextFile
MsgBox ("Registracija veiksmiga.")
Unload Registracija
Else
MsgBox ("Nepareiza administratora parole.")
End If
End Sub
The "aparole" thing is basically just a keyword to enter in a field so only administrators can create new accounts.
accounts.txt content looks like:
1DirectorPassword (ID+jobposition+password)
This is the authentication:
Private Sub logAuth_Click()
Dim TextFile As Integer
Dim FilePath As String
Dim FileContent As String
Dim find As String
Dim result As Integer
find = logID & logAmats & logParole
FilePath = ThisWorkbook.Path & "\accounts.txt"
TextFile = FreeFile
Open FilePath For Input As TextFile
FileContent = Input(LOF(TextFile), TextFile)
result = InStr(FileContent, find)
If result >= 1 Then
MsgBox ("Autorizacija veiksmiga!") ' Success
Unload Autorizacija
End If
Basically when logging in I search within the accounts.txt for the string combo (ID+jobposition+password) which I use when registering. So in general the approach works, but:
If I enter everything perfectly matched = works great
If I enter the password half of it, like in a format of = 1DirectorPass it still works, so basically how can I tell to only search for the whole string and not parts of it?
I think the issue lies within InStr...
You could test for the newline markers in your file content, like this:
result = InStr(vbCrLf & FileContent, vbCrLf & find & vbCrLf)
This will only match complete lines. An extra newline is added before the file content so also the first line can be matched. At the end of the file content you would already have a vbCrLf character, because Print is supposed to add that.

FileSave() Word Macro

I have written a macro which when a file is created and the little save button is clicked the macro is triggered(as it overrides the default FileSave function). The macro extracts information from a table in my document, converts it to a string then cleanses that string of any carriage returns then uses that as the file name. An if statement then runs checking whether a hidden row in the table has a value of 1 and if not then it will set the value to 1 and save the document at the location specified with the new filename.
All of this works great except when I re-open the file to edit it, as my users will do, and click save again it tries to run everything again, completely ignoring my If statements first statement and will add a carriage return to the start of the filename effectively breaking the save function to SharePoint as it has an invalid character in it. If you then click save again it will seemingly run the macro as normal and save it by actually reading the if statement correctly. Am I doing something wrong here?
Here is the code:
Sub FileSave()
Dim strText As String
Dim strClean As String
Dim strFileName As String
Dim strLocation As String
Dim strSavedName As String
Dim strCleanSave As String
strText = ActiveDocument.Tables(1).Rows(1).Cells(2).Range.Text
strClean = Application.CleanString(strText)
strFileName = strClean + "_" + Format(Date, "yyyy-mm-dd")
strLocation = "[My SharePoint Site]"
If ActiveDocument.Tables(1).Rows(1).Cells(3).Range.Text = "1" Then
strSavedName = ActiveDocument.Name
strCleanSave = Application.CleanString(strSavedName)
ActiveDocument.SaveAs FileName:=strSavedName
Exit Sub
Else
ActiveDocument.Tables(1).Rows(1).Cells(3).Range.Text = "1"
ActiveDocument.SaveAs FileName:=strLocation & strFileName & ".docx"
End If
End Sub
Word table cell text ranges are terminated with two hidden chars, a carriage return (ASCII 13) and a bell (ASCII 7). Your IF condition returns false because it is testing the equivalence of "1" and "1" & Chr(13) & Chr(7).
In your case you can limit the test to the first char:
If Left$(ActiveDocument.Tables(1).Rows(1).Cells(3).Range.Text, 1) = "1" Then
More generally you can test the visible cell contents with a Len() - 2.
Hope that helps.