Regular Expression Rules in Outlook 2007? - regex

Is it possible to create rules in Outlook 2007 based on a regex string?
I'm trying to add a filter for messages containing a string such as: 4000-10, a four digit number followed by a dash and then a two digit number, which can be anything from 0000-00 to 9999-99.
I was using this as a regex: \b[0-9]{4}\-[0-9]{2}\b but the filter isn't working. I've tried a few other modifications as well with no luck. I wasn't able to find anything concrete online about whether Outlook even supports entering regexes into a rule, though, so I figured I would ask here in case I'm wasting my time.
EDIT: Thanks to Chris's comment below, I was able to implement this filter via a macro. I thought I would share my code below in case it is able to help anyone else:
Sub JobNumberFilter(Message As Outlook.MailItem)
Dim MatchesSubject, MatchesBody
Dim RegEx As New RegExp
'e.g. 1000-10'
RegEx.Pattern = "([0-9]{4}-[0-9]{2})"
'Check for pattern in subject and body'
If (RegEx.Test(Message.Subject) Or RegEx.Test(Message.Body)) Then
Set MatchesSubject = RegEx.Execute(Message.Subject)
Set MatchesBody = RegEx.Execute(Message.Body)
If Not (MatchesSubject Is Nothing And MatchesBody Is Nothing) Then
'Assign "Job Number" category'
Message.Categories = "Job Number"
Message.Save
End If
End If
End Sub

I do not know if a regex can be used directly in a rule, but you can have a rule trigger a script and the script can use regexes. I hate Outlook.
First, you have to open the script editor via Tools - Macro - Open Visual Basic Editor (Alt-F11 is the shortcut).
The editor will open. It should contain a project outline in a small panel in the top-left corner. The project will be listed as VBAProject.OTM. Expand this item to reveal Microsoft Office Outlook Objects. Expand that to reveal ThisOutlookSession. Double-click ThisOutlookSession to open the code editing pane (which will probably be blank).
Next select Tools menu | References and enable the RegExp references called something like "Microsoft VBScript Regular Expressions 5.5"
You can now create a subroutine to perform your filtering action. Note that a subroutine called by a rule must have a single parameter of type Outlook.MailItem. For example:
' note that Stack Overflow's syntax highlighting doesn't understand VBScript's
' comment character (the single quote) - it treats it as a string delimiter. To
' make the code appear correctly, each comment must be closed with another single
' quote so that the syntax highlighter will stop coloring everything as a string.'
Public Enum Actions
ACT_DELIVER = 0
ACT_DELETE = 1
ACT_QUARANTINE = 2
End Enum
Sub MyNiftyFilter(Item As Outlook.MailItem)
Dim Matches, Match
Dim RegEx As New RegExp
RegEx.IgnoreCase = True
' assume mail is good'
Dim Message As String: Message = ""
Dim Action As Actions: Action = ACT_DELIVER
' SPAM TEST: Illegal word in subject'
RegEx.Pattern = "(v\|agra|erection|penis|boner|pharmacy|painkiller|vicodin|valium|adderol|sex med|pills|pilules|viagra|cialis|levitra|rolex|diploma)"
If Action = ACT_DELIVER Then
If RegEx.Test(Item.Subject) Then
Action = ACT_QUARANTINE
Set Matches = RegEx.Execute(Item.Subject)
Message = "SPAM: Subject contains restricted word(s): " & JoinMatches(Matches, ",")
End If
End If
' other tests'
Select Case Action
Case Actions.ACT_QUARANTINE
Dim ns As Outlook.NameSpace
Set ns = Application.GetNamespace("MAPI")
Dim junk As Outlook.Folder
Set junk = ns.GetDefaultFolder(olFolderJunk)
Item.Subject = "SPAM: " & Item.Subject
If Item.BodyFormat = olFormatHTML Then
Item.HTMLBody = "<h2>" & Message & "</h2>" & Item.HTMLBody
Else
Item.Body = Message & vbCrLf & vbCrLf & Item.Body
End If
Item.Save
Item.Move junk
Case Actions.ACT_DELETE
' similar to above, but grab Deleted Items folder as destination of move'
Case Actions.ACT_DELIVER
' do nothing'
End Select
End Sub
Private Function JoinMatches(Matches, Delimeter)
Dim RVal: RVal = ""
For Each Match In Matches
If Len(RVal) <> 0 Then
RVal = RVal & ", " & Match.Value
Else
RVal = RVal & Match.Value
End If
Next
JoinMatches = RVal
End Function
Next, you have to create a rule (Tools - Rules and Alerts) to trigger this script. Click the New Rule button on the dialog to launch the wizard. Select a template for the rule. Choose the "Check messages when they arrive" template from the "Start from a blank rule" category. Click Next.
Choose the "On this machine only" condition (intuitive isn't it?) and click next.
Choose the "run a script" option. At the bottom of the wizard where it shows your new rule, it should read:
Apply this rule after the message arrives
on this machine only
run a script
The phrase "a script" is a clickable link. Click it and Outlook will display a dialog that should list the subroutine you created earlier. Select your subroutine and click the OK button.
You can click Next to add exceptions to the rule or click Finish if you have no exceptions.
Now, as though that process was not convoluted enough, this rule will deactivate every time you stop and restart Outlook unless you sign the script with a code signing key.
If you don't already have a code signing key, you can create one with OpenSSL.
Did I mention that I hate Outlook?

Microsoft Outlook does not support regular expressions. You can perform wildcard searches, although for some inexplicable reason the wildcard character is %, not *.

Related

Why does this regular expression test give different results for what should be the same body text?

Here's the pertinent code, which is giving different results on the regular expression test for the message body depending on whether I launch it using TestLaunchURL or the message is passed to it by Outlook when an incoming message arrives:
Public Sub OpenLinksMessage(olMail As Outlook.MailItem)
Dim Reg1 As RegExp
Dim AllMatches As MatchCollection
Dim M As Match
Dim strURL As String
Dim RetCode As Long
Set Reg1 = New RegExp
With Reg1
.Pattern = "(https?[:]//([0-9a-z=\?:/\.&-^!#$;_])*)"
.Global = True
.IgnoreCase = True
End With
PlayTheSound "Speech On.wav"
RetCode = Reg1.Test(olMail.Body)
MsgBox "The RetCode from Reg1.Test(olMail.Body) equals" + Str(RetCode)
' If the regular expression test for URLs in the message body finds one or more
If RetCode Then
PlayTheSound "chimes.wav"
' Use the RegEx to return all instances that match it to the AllMatches group
Set AllMatches = Reg1.Execute(olMail.Body)
For Each M In AllMatches
strURL = M.SubMatches(0)
' Don't activate any URLs that are for unsubscribing; skip them
If InStr(1, strURL, "unsubscribe") Then GoTo NextURL
' If the URL ends with a > from being enclosed in darts, strip that > off
If Right(strURL, 1) = ">" Then strURL = Left(strURL, Len(strURL) - 1)
' The URL to activate to accept must contain both of the substrings in the IF statement
PlayTheSound "tada.wav"
If InStr(1, strURL, ".com") Then
PlayTheSound "TrainWhistle.wav"
' Activate that link to accept the job
RetCode = ShellExecute(0, "Open", strURL)
Set Reg1 = Nothing
Exit Sub
End If
NextURL:
Next
End If
Set Reg1 = Nothing
End Sub
Private Sub TestLaunchURL()
Dim currItem As MailItem
Set currItem = ActiveExplorer.Selection(1)
OpenLinksMessage currItem
End Sub
The test IF Reg1.Test(olMail.Body) always returns a 0 when invoked from an Outlook rule on an incoming message and always returns a -1 when I use the debugger to trigger it for that same message from my inbox.
The code is acting almost as though it has a null message body when it is triggered by an Outlook rule versus having the message body when kicked off by me from exactly the same message once it's in my inbox.
I am completely flummoxed, as I can't understand how one and the same message, with one and the same body, can give 2 different results depending on who hands the message to the subroutine.
Additional Debugging Information:
Since the issue appears to surround the value of the Body of the message, I added the following code, that also examines the HTMLBody as well:
If IsNull(olMail.Body) Then
MsgBox "The message body is null!!"
Else
MsgBox "BODY: " + "|" + olMail.Body + "|"
End If
If IsNull(olMail.HTMLBody) Then
MsgBox "The message HTMLbody is null!!"
Else
MsgBox "BODY: " + "|" + olMail.HTMLBody + "|"
End If
When the script is triggered by the Outlook rule on a message with the content, and only the content, "http://britishtoolworks.com", when it arrives these are the two message boxes:
[I am being forbidden to post images for some reason. These show absolutely nothing between the two pipe characters for BODY and some text, but nothing with the URL in it, for the HTMLBody]
while these are the message boxes if I trigger the script via TestLaunchURL after that very same message is sitting in my inbox:
[Shows the actual expected content. I am forbidden from posting more images.]
If anyone can explain this discrepancy, please do.
Here is the code that finally works. It's clear that the .Body member of olMail is not available until some sort of behind the scenes processing has had time to occur and if you don't wait long enough it won't be there when you go to test using it. Focus on the Public Sub OpenLinksMessage which is where the problem had been occurring.
The major (and only) change that allowed the expected processing of olMail.Body to take place, apparently, was the addition of the line of code: Set InspectMail = olMail.GetInspector.CurrentItem. The time it takes for this set statement to run allows the .Body to become available on the olMail parameter that's passed in by the Outlook rule. What's interesting is that if you immediately display InspectMail.Body after the set statement it shows as empty, just like olMail.Body used to.
Option Explicit
Private Declare Function ShellExecute _
Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hWnd As Long, _
ByVal Operation As String, _
ByVal Filename As String, _
Optional ByVal Parameters As String, _
Optional ByVal Directory As String, _
Optional ByVal WindowStyle As Long = vbMinimizedFocus _
) As Long
Public Sub OpenLinksMessage(olMail As Outlook.MailItem)
Dim InspectMail As Outlook.MailItem
Dim Reg1 As RegExp
Dim AllMatches As MatchCollection
Dim M As Match
Dim strURL As String
Dim SnaggedBody As String
Dim RetCode As Long
' The purpose of the following Set statement is strictly to "burn time" so that the .Body member of
' olMail is available by the time it is needed below. Without this statement the .Body is consistently
' showing up as empty. What's interesting is if you use MsgBox to display InspectMail.Body immediately after
' this Set statement it shows as empty.
Set InspectMail = olMail.GetInspector.CurrentItem
Set Reg1 = New RegExp
With Reg1
.Pattern = "(https?[:]//([0-9a-z=\?:/\.&-^!#$;_])*)"
.Global = True
.IgnoreCase = True
End With
RetCode = Reg1.Test(olMail.Body)
' If the regular expression test for URLs in the message body finds one or more
If RetCode Then
' Use the RegEx to return all instances that match it to the AllMatches group
Set AllMatches = Reg1.Execute(olMail.Body)
For Each M In AllMatches
strURL = M.SubMatches(0)
' Don't activate any URLs that are for unsubscribing; skip them
If InStr(1, strURL, "unsubscribe") Then GoTo NextURL
' If the URL ends with a > from being enclosed in darts, strip that > off
If Right(strURL, 1) = ">" Then strURL = Left(strURL, Len(strURL) - 1)
' The URL to activate to accept must contain both of the substrings in the IF statement
If InStr(1, strURL, ".com") Then
' Activate that link to accept the job
RetCode = ShellExecute(0, "Open", strURL)
Set InspectMail = Nothing
Set Reg1 = Nothing
Set AllMatches = Nothing
Set M = Nothing
Exit Sub
End If
NextURL:
Next
End If
Set InspectMail = Nothing
Set Reg1 = Nothing
Set AllMatches = Nothing
Set M = Nothing
End Sub
Special thanks to niton for his patience and assistance on other questions that formed the basis for this one. He led me to the solution.
Addendum: Another individual assisting me elsewhere brought up something that deserves noting here, as I think she's got it right. I am using Gmail via IMAP access to download my messages. What appears to be happening is that once the header information is populated into the MailItem object, the Outlook Rule is immediately being triggered. The rest of the members of that object, including .Body, appear to be being populated asynchronously behind the scenes. The speed of processing in your script versus the speed of population processing can lead to situations where the script is triggered with the header information and gets to the point where it accesses the .Body before it's been populated by Outlook itself. What's interesting is when this occurred, and that was most of the time until this solution was found, .Body was not considered to be NULL. The IsNull test never passed, but the content when printed was nothing, as in absolutely nothing between the two pipe characters I used as delimiters. What is "nothing that takes up any characters" but that also is not NULL?
Clearly the whole MailItem passed would not pass the "Is Nothing" test, and I would not think to test an individual member of an object with "Is Nothing."
For myself, I consider this to be buggy. Before a MailItem object is ever handed off for script processing it would be the logical presumption that all Members of that object that can be prepopulated will be prepopulated by Outlook before the handoff. It just doesn't appear to be happening that way, and this is under Outlook 2010 on my machine and Outlook 2016 on another. If you get a member that has not yet been populated it should always have the NULL value, as that should be what everything is initialized to prior to the population process taking place.

Search while selection enabled in macro VS2015

On VAX/VMS (or OpenVMS Alpha and its other names) there was an editor called TPU. In TPU you could enable selection of text independently of holding a key down. You pressed SELECT and then any cursor movement you made selected text between the editing point and the new cursor location.
You could also record macros. So you could use this text selection feature to create macros like:
find "abc"
select
find "xyz"
cut
stop recording
So this macro would find any line with "abc" in it and then cut all text between "abc" and "xyz". Massive time saver.
Making sense? How can I do that in VS2015? I can't find a macro extension that provides the selection behaviour I need to do this.
Cheers,
.pd.
EDIT
It occurred to me this could be done with a regex but it seems like a pretty big ask.
#Html.DropDownListFor(m => m.Property, Model.SelectListProperty, htmlAttributes: new { #class="whatever" })
// the regex would replace this with
#Html.MyDropDownListFor(m => m.Property, Model.SelectListProperty, Model.Property, htmlAttributes: new { #class="whatever"})
So I would be looking for a regex to
- find #Html.DropDownList
- replace the token 1 of that line split by ',' with token 1 of token 0 split by '.' and prefixed with "Model."
Assuming Model.Property comes from m => m.Property.
Search for
#Html\.DropDownListFor\(((\w+)\s*=>\s*\2\.(\w+)),\s*(Model\.\w+)(,(?:[^(){}]|\{[^{}]*\})*)?\)
Replace with
#Html.MyDropDownListFor($1, $4, Model.$3$5)
Demo: http://regexr.com/3f3io

Find-Replace text contained in textboxes and tables

I'm hoping I can get come help from a programmer.
What I want to do is to translate a word report generated by a software, so I turned to macros. I already have a word file containing the original word/phrases and the translated ones.
I 'stole' the code to translate from some forum online, which works great with normal text. My problem is that the text of the report I want to translate is within various "text boxes" and "tables".
I was able to manually remove the tables, but keep the text. This totally ruined the formatting, but I can deal with that latter.
Now, unfortunately I cannot do the same with textboxes. There is no 'delete, but keep the text" function for textboxes.
I can send you the macro code, the original report automatically generated by the software and the file to get all translated words from.
I really appreciate your time.
Ok. This is code that translates normal text.
Sub Translate()
Dim oChanges As Document, oDoc As Document
Dim oTable As Table
Dim oRng As Range
Dim rFindText As Range, rReplacement As Range
Dim i As Long
Dim sFname As String
'Change the path in the line below to reflect the path of the table document
sFname = "C:\Users\user\Desktop\Dictionary.doc"
Set oDoc = ActiveDocument
Set oChanges = Documents.Open(FileName:=sFname, Visible:=False)
Set oTable = oChanges.Tables(1)
For i = 1 To oTable.Rows.Count
Set oRng = oDoc.Range
Set rFindText = oTable.Cell(i, 1).Range
rFindText.End = rFindText.End - 1
Set rReplacement = oTable.Cell(i, 2).Range
rReplacement.End = rReplacement.End - 1
With oRng.Find
.ClearFormatting
.Replacement.ClearFormatting
Do While .Execute(findText:=rFindText, _
MatchWholeWord:=True, _
MatchWildcards:=False, _
Forward:=True, _
Wrap:=wdFindContinue) = True
oRng.Text = rReplacement
Loop
End With
Next i
oChanges.Close wdDoNotSaveChanges
End Sub
I'm guessing you'd need to see the format of the document that is being translated, which contains all the tables and text boxes. But it is too large and I'm not sure if I can send it as an attachment here somehow. (sorry, its my first time on this forum). Any advise?
Thanks a lot
JD

Regex to remove double-quotes in CSV fields that are delineated by double-quotes

This is for a VB.NET project. My existing method converts a comma-delimited file to a pipe-delimited file. It got a little challenging because some of the fields had commas within them, so those fields had double-quotes around the fields contents.
Here's the working code (thanks a million to The Blue Dog for the research on this):
Private Function ConvertCommaSepToPipeSep() As Boolean
Dim line, result As String
Dim pattern As String = ",([^,""]*(?:""[^""]*"")?[^,""]*)(?=,|$)"
Dim replacement As String = "|$1"
Dim rgx As New Regex(pattern)
'Console.WriteLine("Conversion start time: " & DateTime.Now.ToLongTimeString())
Try
Using sw As New StreamWriter("output.csv")
Using sr As New StreamReader("source.csv")
While Not sr.EndOfStream
line = sr.ReadLine
result = rgx.Replace(line, replacement)
sw.WriteLine(result.Replace(Chr(34), ""))
End While
End Using
End Using
Catch ex As Exception
MessageBox.Show("There was a problem converting the file." & vbcrlf & ex.message)
Return False
End Try
'Console.WriteLine("Conversion end time: " & DateTime.Now.ToLongTimeString())
Return True
End Function
I found out, however, that some of the fields have double-quotes within them as well.
Here are some sample lines from the source file that I am converting.
122749,JOHN DOE,ACS155,7/5/2014,P,SCH/RC Activation Week 2,HRLY,1299577,Scheduler IT,2204,CVISA-Client Activation,1220000,Svcs Clin Implement,34
110310,JANE DOE,ACS150,2/8/2014,P,"Developed Employee Interface""",HRLY,1267305,Project Management - Client Implementation Services,2500,PJM -Project Management,1410000,Tech Services Development,8
110310,MARY DOE,ACS160,2/8/2014,P,EDManage+ CSV data extract,HRLY,1527401,Project Management - Client Implementation Services,2500,PJM -Project Management,1410000,Tech Services Development,8
129084,ROBERT SMITH,ACS80,9/27/2014,P,,PTO,0,Company General Services,1030,"Time Off - PTO, Holiday, Personal Holiday, FTO",1100000,Client Services Technical,40
117592,HARRY JOHNSON,ACS64,5/10/2014,P,"helped penny post AP ""E"" cks",HRLY,1554404,General Financials IT,2120,CCON-Client Conference Call,1100000,Client Services Technical,1.5
110310,MARK WILSON,ACS130,2/8/2014,P,"""Charge Vs Payment""",HRLY,1267305,Project Management - Clinical Implementation Services,2500,PJM -Project Management,1410000,Tech Services Development,8
Those same rows need to be converted to look like this:
122749|JOHN DOE|ACS155|7/5/2014|P|SCH/RC Activation Week 2|HRLY|1299577|Scheduler IT|2204|CVISA-Client Activation|1220000|Svcs Clin Implement|34
110310|JANE DOE|ACS150|2/8/2014|P|Developed Employee Interface""|HRLY|1267305|Project Management - Client Implementation Services|2500|PJM -Project Management|1410000|Tech Services Development|8
110310|MARY DOE|ACS160|2/8/2014|P|EDManage+ CSV data extract|HRLY|1527401|Project Management - Client Implementation Services|2500|PJM -Project Management|1410000|Tech Services Development|8
129084|ROBERT SMITH|ACS80|9/27/2014|P||PTO|0|Company General Services|1030|Time Off - PTO, Holiday, Personal Holiday, FTO|1100000|Client Services Technical|40
117592|HARRY JOHNSON|ACS64|5/10/2014|P|helped penny post AP E cks|HRLY|1554404|General Financials IT|2120|CCON-Client Conference Call|1100000|Client Services Technical|1.5
110310|MARK WILSON|ACS130|2/8/2014|P|Charge Vs Payment|HRLY|1267305|Project Management - Clinical Implementation Services|2500|PJM -Project Management|1410000|Tech Services Development|8
In this CSV, columns that have commas in the text are given double-quotes around the column and the regex above accounts for that. But I found out that some fields also have double-quotes within them. Any instances of double-quotes within a field can be removed, but in some cases the field can end or start with a double quote, resulting in three double-quotes, but I can't just remove all double-quotes because they help delineate where fields that have commas in them start and end.
What needs to be added to the regex to do that?
The "" are supposed to be converted to a single ". Are you sure you want to remove them completely?
– nhahtdh
Can't you just csvString = csvString.Replace( ... ) before you run the RE
– Alex K.

VB.net - Get the "real" character on PreviewKeyDown/KeyPress Event from e.Key

I'm facing some difficulties building my inputfilter for a TextBox (WFP, but this should not matter here)
I need to suppress all inputs, but:
-Digits (Integer - no floats, groups)
-Basic Math Symbols: (,),+,-,*,/
Yes, the goal is to have a kind of formula evaluation. This part works well with the Sriptcontrol-Method
(from: http://www.vb-tips.com/Eval.aspx)
I also found several leads to filter by Regex (sorry, can't remember the source...):
Regex("[\d\(\)\+\-\*\/]")
Now the problem is, that e.g. the Character '+' never shows up in the e.Key Argument
Pressing NumPad-'+' gives me
e.Key=85
e.Key.Tostring=Add
Trying from Keyboard '+'
e.Key=141
e.Key.ToString=OemPlus
So the Regex will never find a match to these allowed chars.
Since Windows recognizes the Key I hit and places the Character I see on my Keyboard, I guess, there MUST be a way to catch "exactly" the Key, which the user means to hit :-)
Also I needed to suppress any modifier-chars (such as AltGr+2: square on my german layout)
Here my Code Pasted (might be a little bit messy due to experimenting around)
Private Sub tb_PreviewKeyDown(sender As System.Object, e As System.Windows.Input.KeyEventArgs) Handles TB0.PreviewKeyDown
Dim s As TextBox = sender
Debug.WriteLine(e.Key.ToString & " -" & e.Key)
Dim rx As New Regex("[\d\(\)\+\-\*\/]|Add|Subtract|Divide|Multiply|Return|Tab|Back")
If Not rx.IsMatch(e.Key.ToString) Then
e.Handled = True
Return
End If
If e.Key = Key.Enter Or e.Key = Key.Tab Then
If s.Text.Trim = "" Then Return
If Not IsNumeric(s.Text) Then
Try
Dim ev As New MSScriptControl.ScriptControl
ev.Language = "VBScript"
ev.AllowUI = False
ev.Reset()
s.Text = CStr(Math.Round(CDbl(ev.Eval(s.Text))))
s.SelectAll()
Catch ex As Exception
s.SelectAll()
e.Handled = True
Return
End Try
End If
s.SelectAll()
End If
End Sub
Any hints on this are highly appreciated.
Thanks in advance,
Daniel
The comment of Hans Passant solved my issue!