Outlook – Anvil of Time https://anvil-of-time.com Nature Forges Everything on the Anvil of Time Thu, 11 Mar 2021 17:35:59 +0000 en-US hourly 1 Sending iCalendar events via a .Net Assembly and SQLServer stored procedure https://anvil-of-time.com/ms-sql-server/sending-icalendar-events-via-a-net-assembly-and-sqlserver-stored-procedure/ Tue, 31 Jul 2012 22:01:12 +0000 http://anvil-of-time.com/wordpress/?p=1446 As I’ve mentioned in earlier posts, the iCalendar format is a standard way to send event information to a variety of email/scheduling systems (Outlook, Google, etc.). Here is a way to set it up so that you can simply call a stored procedure to send the event. This assumes you have SQL Server set up with access to Exchange.

In C# (I’m using Visual Studio 2008) create a class as indicated.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
using System.Net.Mail;
using System.Net.Mime;

namespace icalEmail
{
    public class StoredProcedures
    {
        [SqlProcedure]
        public static void SendiCal(DateTime startDateTime, DateTime endDateTime, string emailSubject,
           string userEmailAddress, string cnAttendee, string methodType, DateTime createdOn, string notes, string UID, string location, string seqNo, string smtpserver, int smtpport)
        {
            // create the iCal string in the proper format
            string iCal = CreateiCalFormat(startDateTime, endDateTime, emailSubject, userEmailAddress,
            cnAttendee, methodType, createdOn, UID, notes, location, seqNo);
            // create the MIME protocol Content-Type 
            var calendarType = new ContentType("text/calendar;method=REQUEST; charset=UTF-8");
            calendarType.Parameters.Add("method", "REQUEST");

            AlternateView caledarView = AlternateView.CreateAlternateViewFromString(iCal, calendarType);
            caledarView.TransferEncoding = TransferEncoding.SevenBit;
            // connect to smtpserver
            var client = new SmtpClient(smtpserver, smtpport);
            // create the email
            var mailMesage = new MailMessage();
            mailMesage.From = new MailAddress("donotreply@email.com");
            mailMesage.To.Add(new MailAddress(userEmailAddress));
            mailMesage.Subject = emailSubject;
            mailMesage.Body = iCal;
            mailMesage.AlternateViews.Add(caledarView);
            client.Send(mailMesage);
        }

        private static string _dateFormat = "yyyyMMddTHHmmssZ";
        private static string CreateiCalFormat(DateTime startDateTime, DateTime endDateTime,
        string emailSubject, string userEmailAddress, string cnAttendee, string methodType, DateTime createdOn, string UID,
        string notes, string location, string seqNo)
        {
            string iCal =

            "BEGIN:VCALENDAR" +

            "\nPRODID:-//SampleApp//AoT//EN" +

            "\nVERSION:2.0" +
            /// REQUEST for add or update, CANCEL for cancellation
            "\nMETHOD:" + methodType +

            "\nBEGIN:VEVENT" +

            "\nORGANIZER:CN='Matt'" +

            //"\nORGANIZER:MAILTO:matt.balent@anvil-of-time.com" +

            "\nATTENDEE;CN='" + cnAttendee + "'" +

            ";ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=" + userEmailAddress + 

            "\nDTSTART:" + startDateTime.ToUniversalTime().ToString(_dateFormat) +

            "\nDTEND:" + endDateTime.ToUniversalTime().ToString(_dateFormat) +

            "\nSTATUS:CONFIRMED" +

            "\nTRANSP:OPAQUE" +
            /// 0 for add, 1 for update or cancellation
            "\nSEQUENCE:" + seqNo +

            "\nUID:" + Guid.NewGuid() +
            /// resend original UID for updates and cancellations
            //"\nUID:" + UID +

            "\nDTSTAMP:" + createdOn.ToUniversalTime().ToString(_dateFormat) +

            "\nLAST-MODIFIED:" + createdOn.ToUniversalTime().ToString(_dateFormat) +

            "\nLOCATION:" + location +

            "\nDESCRIPTION:" + notes +

            "\nSUMMARY:" + emailSubject +

            "\nPRIORITY:5" +

            "\nCLASS:PUBLIC" +

            "\nEND:VEVENT" +

            "\nEND:VCALENDAR";

            return iCal;
        }

    }
}

This would be compiled into a .dll which needs to be registered in SQL Server. This assembly is to be marked as UNSAFE so someone with sysadmin priviges needs to register it.

CREATE ASSEMBLY [eventEmail]
AUTHORIZATION [dbo]
FROM 'C:\temp\icalemail.dll'
WITH PERMISSION_SET = UNSAFE

There is an interesting discussion of ‘UNSAFE’ assemblies on Stackoverflow.

Now create the stored procedure which will call the assembly.

create procedure dbo.usp_mb_eventnotify(@startDateTime datetime
				, @endDateTime datetime
				, @emailSubject nvarchar(80)
				, @emailSummary nvarchar(1000)
				, @location nvarchar(255)
				, @attendeeName nvarchar(100)
				, @attendeeEmail nvarchar(255)
				, @organizerName nvarchar(100)
				, @organizerEmail nvarchar(255)
				, @UID nvarchar(255)
				, @status nvarchar(12)
				, @requestType nvarchar(12)
				, @smtpserver nvarchar (255)
				, @smtpport bigint)
as external name [iCalEmail].[iCalEmail.Storedprocedures].[SendiCal];

Examples of use:

Create the event

exec dbo.usp_mb_eventnotify  
'2012-08-08 13:00:00',
'2012-08-08 15:00:00',
'BigProj 2',
'New schedule of Event sent by Matt',
'CONF01',
'Balent, Matt',
'matt.balent@anvil-of-time.com',
'AoT',
'donotreply@email.com',
'AOT372711c',
'CONFIRMED',
'REQUEST',
'1234.na.corp.aot.com',
25

Update the event

exec dbo.usp_mb_eventnotify				
'2012-08-08 10:00:00',
'2012-08-08 12:00:00',
'BigProj 2',
'New schedule of Event sent by Matt',
'CONF01',
'Balent, Matt',
'matt.balent@anvil-of-time.com',
'AoT',
'donotreply@email.com',
'AOT372711c',
'CONFIRMED',
'REQUEST',
'1234.na.corp.aot.com',
25

Cancel the event

exec dbo.usp_mb_eventnotify 
'2012-08-08 10:00:00',
'2012-08-08 12:00:00',
'BigProj 2',
'New schedule of Event sent by Matt',
'CONF01',
'Balent, Matt',
'matt.balent@anvil-of-time.com',
'AoT',
'donotreply@email.com',
'AOT372711c',
'CANCELLED',
'CANCEL',
'1234.na.corp.aot.com',
25
]]>
PowerBuilder – Reading Outlook Items https://anvil-of-time.com/powerbuilder/powerbuilder-reading-outlook-items/ Fri, 02 Sep 2011 01:20:40 +0000 http://anvil-of-time.com/wordpress/?p=855 Here is some code which reads data from Outlook (2007 was tested) into PowerBuilder. You basically need to create a window with a multiline edit and a button on it. Put this into the clicked event of the button.

To run it, open Outlook, select something (email message, task, etc.) then click the button on your window. There are many, many more methods and properties you have access to from PB via OLE to Outlook. The MSDN reference online is a big help

integer li_rc
long ll_itemcount, ll_i
oleobject lole_item, lole_outlook, lole_exp, lole_selecteditems
string ls_subject, ls_from, ls_to, ls_body, ls_msg
lole_outlook = CREATE oleobject
lole_exp = CREATE oleobject
lole_selecteditems = CREATE oleobject
li_rc = lole_outlook.ConnectToNewObject("outlook.application")
lole_exp = lole_outlook.ActiveExplorer()
// Outlook has to be running
If IsNull(lole_exp) THEN
	Messagebox('Outlook Error','Is Outlook currently running?')
	GOTO cleanup
END IF
li_rc = lole_exp.class
// caption is window name like "Inbox - Microsoft Outlook" or "Calendar - Microsoft Outlook"
ls_subject = lole_exp.caption

lole_selecteditems = lole_exp.selection
ll_itemcount = lole_selecteditems.count

FOR ll_i = 1 to ll_itemcount
	lole_item = CREATE oleobject
	lole_item = lole_exp.selection.item(ll_i)
	li_rc = lole_item.class
	CHOOSE CASE li_rc
		CASE 26 //appointment
			ls_body = lole_item.body
			ls_msg += '~r~n Appointment No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_body
			// lots of other stuff could be here
		CASE 40 // contact
			ls_body = lole_item.body
			ls_msg += '~r~n Contact No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_body
			
		CASE 43 // mail
			ls_subject = lole_item.subject
			ls_from = lole_item.sendername
			ls_to = lole_item.to
			ls_body = lole_item.body
			ls_msg = '~r~nSubject: ' + ls_subject + '~r~nFrom: ' + ls_from + '~r~nTo: ' + ls_to + '~r~nBody: ' + ls_body
			ls_msg += '~r~n Email No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_msg
			
		CASE 48 // task
			ls_subject = lole_item.subject
			ls_to = lole_item.owner
			ls_body = lole_item.body
			ls_msg = '~r~nSubject: ' + ls_subject + '~r~nOwner: ' + ls_to + '~r~nBody: ' + ls_body
			ls_msg += '~r~n Task No: ' + string(ll_i) + ' of ' + string(ll_itemcount) + '~r~n' + ls_msg
			
	END CHOOSE
	DESTROY lole_item
NEXT
IF Len(ls_msg) > 0 THEN
	mle_1.text = ls_msg
ELSE
	mle_1.text = 'No items processed.'
END IF
cleanup:
DESTROY lole_selecteditems
DESTROY lole_exp
DESTROY lole_outlook
]]>
VBA – Processing ICS calendar file attachments https://anvil-of-time.com/programming/vba-processing-ics-calendar-file-attachments/ https://anvil-of-time.com/programming/vba-processing-ics-calendar-file-attachments/#respond Thu, 28 Jul 2011 01:20:01 +0000 http://anvil-of-time.com/wordpress/?p=837 This article brings to conclusion the Outlook VBA macro/script I wrote to process event scheduling attachments (.ICS or ICalendar files). The first article shows how to process emails and remove their associated attachments. The second shows how to open the files and both read the file contents into an array as well as write (append) to the file. This code works in Outlook 2007.

The script does the following:

1. Check if the email has an attachment
2. If the attachment is an ICalendar (.ICS) file, remove it to a common folder and update the email with a link to the detached file.
3. Loop through the ICS files in the designated folder and load them into an array.
4. If the last element in the array contains the value “PROCESSED”, skip it
5. Take the UID (Unique IDentifier) array element and turn it into an Outlook Event GUID.
6. Check if there are any events which have the UID Guid. If there are, this indicates the new file is an update to a previously processed event.
7. If there is no previous event with the UID Guid, add the event using the UID Guid as an identifier.
8. If there is a previous event and the new notice is a cancellation, delete the event.
9. If there is a previous event and the new notice is not a cancellation, update the event.
10. Append a line to the ICS file with the value “PROCESSED”.

Public Sub SaveICSAttachmentsMacro()
    Dim objOL As Outlook.Application
    Dim objitem As Outlook.MailItem 'Object
    Dim objAttachments As Outlook.Attachments
    Dim objSelection As Outlook.Selection
    Dim i As Long
    Dim lngCount As Long
    Dim strFile As String
    Dim strFolderpath As String
    Dim strDeletedFiles As String
    Dim strArray() As String 
    Dim ac As Long
    Dim lSeq As Long
    Dim lPriority As Long
    Dim strLine As String
    Dim strField As String
    Dim strValue As String
    Dim strMethod As String
    Dim strUid As String
    Dim strSummary As String
    Dim strLocation As String
    Dim strDesc As String
    Dim strOrganizer As String
    Dim strDate As String
    Dim dtStart As Date
    Dim dtEnd As Date
    
     Dim hexUID
     
    ' Instantiate an Outlook Application object.
    Set objOL = Application
     
     Dim objNs As Outlook.NameSpace
     Set objNs = objOL.Session
     Dim objFolder As Outlook.MAPIFolder
     
     Set objFolder = objNs.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)
     Dim objAppts As Outlook.Items
     Set objAppts = objFolder.Items
     Dim objAppt As Outlook.AppointmentItem
     Dim strFilter As String
     
     
     
    ' Get the path to your My Documents folder
    strFolderpath = CreateObject("WScript.Shell").SpecialFolders(16)
    On Error Resume Next


    ' Get the collection of selected objects.
    Set objSelection = objOL.ActiveExplorer.Selection
''''''''''''''''''''
'[snip] see code from Removing attachments post
'''''''''''''''''''
    ' call function in different module
    ' this populates the string array from the iCalendar file
    icsFileToArray strFile, strArray
    
    ' check last element of array to determine if file previously processed
    If strArray(UBound(strArray)) = "PROCESSED" Then
        'file already processed
        GoTo ExitSub
    Else
        ' loop through array to process event
        For ac = 1 To UBound(strArray)
            ' separate data pairs
            strLine = strArray(ac)
            If InStr(1, strLine, ":") > 0 Then
                strField = Mid(strLine, 1, InStr(1, strLine, ":") - 1)
                strValue = Mid(strLine, InStr(1, strLine, ":") + 1)
            End If
            
            Select Case strField
                Case "METHOD"
                 ' REQUEST or CANCEL
                    strMethod = strValue
                Case "UID"
                 ' Unique identifier for event
                    strUid = strValue
                Case "DTSTART"
                    ' pull out values into 'mm/dd/yyyy hh:mm:ss' format
                    strDate = (Mid(strValue, 5, 2) & "/" & Mid(strValue, 7, 2) & _
                    "/" & Mid(strValue, 1, 4) & " " & Mid(strValue, 10, 2) & ":" & _
                    Mid(strValue, 12, 2))
                    dtStart = CDate(strDate)
                    ' put datetime into Outlook format
                    dtStart = Format(dtStart, "\#m\/d\/yyyy h:mm:ss AM/PM\#")
                Case "DTEND"
                    ' pull out values into 'mm/dd/yyyy hh:mm:ss' format
                    strDate = (Mid(strValue, 5, 2) & "/" & Mid(strValue, 7, 2) & _
                    "/" & Mid(strValue, 1, 4) & " " & Mid(strValue, 10, 2) & ":" & _
                    Mid(strValue, 12, 2))
                    dtEnd = CDate(strDate)
                    dtEnd = Format(dtEnd, "\#m\/d\/yyyy h:mm:ss AM/PM\#")
                Case "SUMMARY"
                    strSummary = strValue
                Case "LOCATION"
                    strLocation = strValue
                Case "ORGANIZER"
                    ' can not set this as of 6-30-2011
                    strOrganizer = strValue
                Case "PRIORITY"
                    lPriority = Val(strValue)
                Case "SEQUENCE"
                 ' if an update must be > 0
                    lSeq = Val(strValue)
                Case "DESCRIPTION"
                    strDesc = strValue
                
                Case Else
                
            End Select
        Next
    End If
    ' check if appointment already exists for given UID
    'convert UID to hex. this should match the GlobalAppointmentID if the item was
    ' imported manually
    'Start with the standard preamble (original example had 26 and not 12 here)=======vv
    hexUID = "040000008200E00074C5B7101A82E0080000000000000000000000000000000000000000120000007643616C2D55696401000000"
    'Now add the provided string
    For i = 1 To Len(strUid)
        hexUID = hexUID & CStr(Hex(Asc(Mid(strUid, i, 1))))
    Next
    'Terminate the UID
    hexUID = hexUID & "00"
    ' this filter used if the new field is set to the GlobalAppointmentID
    strFilter = "[UID] = '" & hexUID & "'"
    Set objAppt = objAppts.Find(strFilter)
    If objAppt Is Nothing Then
        'New event
         Dim myItem As Outlook.AppointmentItem
         Set myItem = objOL.CreateItem(olAppointmentItem)
         myItem.Start = dtStart
         myItem.End = dtEnd
         myItem.Subject = strSummary
         myItem.Location = strLocation
         myItem.Body = strDesc
	 ' this was not working when I did this
         ' myItem.Organizer = strOrganizer
	 ' add a new property "UID" to the event so it can be updated
         myItem.ItemProperties.Add "UID", olText, True
         myItem.Save
         myItem.ItemProperties.Item("UID").Value = hexUID
         myItem.Save
    Else
        If strMethod = "CANCEL" Then
            ' remove item from calendar
            objAppt.Delete
        ElseIf lSeq > 0 Then
            ' update must have sequence > 0
            objAppt.Start = dtStart
            objAppt.End = dtEnd
            objAppt.Subject = strSummary
            objAppt.Location = strLocation
            objAppt.Body = strDesc
            objAppt.Save
        End If
        
    End If
    ' mark file processed
    icsWriteToFile strFile
    
ExitSub:

Set objAttachments = Nothing
Set objitem = Nothing
Set objSelection = Nothing
Set objOL = Nothing
End Sub

The biggest issue I came across in dealing with Outlook is the ability to update or delete a previously scheduled event. One would think Microsoft could handle the setting of an ID which could then be used to find it consistently. Outlook does assign an ID to events but these get changed if you move them around. The ‘GlobalAppointmentID’ does get set (as a GUID) and remains constant but you have no way of searching for it (sigh). Anyway I get around this by adding a property to any Event created via this process and assign the UID (which is converted into a GUID) to it. This way I can do a simple ‘Find’ of my apppointments and process appropriately. Here are some links related to GUIDs and Outlook

MSDN Outlook GUID stuff

Changing a string into an Outlook Event GUID See answer by banjaxed.

Note this process does not handle recurring appointments

]]>
https://anvil-of-time.com/programming/vba-processing-ics-calendar-file-attachments/feed/ 0
VBA – Reading a file into an array and Appending to a file https://anvil-of-time.com/programming/vba-reading-a-file-into-an-array-and-appending-to-a-file/ Tue, 12 Jul 2011 01:20:30 +0000 http://anvil-of-time.com/wordpress/?p=833 Both of these routines can be called from Outlook Macros/Scripts to process files. In my case I am using them to open iCalendar (ICS) files and place the contents into a string array. The calling method then processes the information then flags the file as ‘PROCESSED’ by the second routine.

Public Sub icsFileToArray(ByVal FileName As String, ByRef TheArray As Variant)
'
Dim oFSO
Set oFSO = CreateObject("Scripting.FileSystemObject")
Dim oFSTR As Scripting.TextStream
Dim lCtr As Long

Set oFSTR = oFSO.OpenTextFile(FileName, 1, 0) ' for reading

Do While Not oFSTR.AtEndOfStream
    ReDim Preserve TheArray(lCtr) As String
    TheArray(lCtr) = oFSTR.ReadLine
    lCtr = lCtr + 1
    DoEvents
Loop
oFSTR.Close
ErrorHandler:
Set oFSTR = Nothing
End Sub


Public Sub icsWriteToFile(ByVal FileName As String)
'
Dim oFSO
Set oFSO = CreateObject("Scripting.FileSystemObject")
Dim oFSTR As Scripting.TextStream
'Dim ret As Long
Dim lCtr As Long

Set oFSTR = oFSO.OpenTextFile(FileName, 8, 0) ' for appending
oFSTR.Write "PROCESSED"
oFSTR.Close
ErrorHandler:
Set oFSTR = Nothing
End Sub
]]>
VBA – Removing attachments from Outlook email https://anvil-of-time.com/programming/vba-removing-attachments-from-outlook-email-2/ Sat, 02 Jul 2011 01:20:57 +0000 http://anvil-of-time.com/wordpress/?p=806 One very powerful aspect of the Microsoft Office Suite of applications is the ability to create Macros and Scripts to perform various processes. This gives those of us who worked in Visual Basic 6 a chance to dust off our skills since the ‘flavor’ of Visual Basic used in the Microsoft Office applications (aptly called Visual Basic for Applications) is essentially the same.

A common request in Outlook is to process mail messages with attachments by removing the attachments and saving them to some specified folder. This can be accomplished with either a macro or a script. The script has an advantage of being something you can put into a rule so any incoming emails are processed automatically. Macros allow you to process only specific emails should you so desire.

My examples below are set to remove external calendar request files (.ICS or ICalendar format) and place them into the folder ‘ICSFiles’ set up in the standard ‘My Documents’ folder.

Public Sub SaveICSAttachments()
' to set this as a script the declaration would be
' Public Sub SaveICSAttachments(objitem As MailItem)
Dim objOL As Outlook.Application
''''''''''''''''''''''''''''''''''''
' the next line removed if set up as a script
Dim objitem As Outlook.MailItem 'Object
'''''''''''''''''''''''''''''''''''
Dim objAttachments As Outlook.Attachments
Dim objSelection As Outlook.Selection
Dim i As Long
Dim lCount As Long
Dim sFile As String
Dim sFolderpath As String
Dim sDeletedFiles As String

' Instantiate an Outlook Application object.
Set objOL = Application

Dim objNs As Outlook.NameSpace
Set objNs = objOL.Session
Dim objFolder As Outlook.MAPIFolder

' Get the path to your My Documents folder
sFolderpath = CreateObject("WScript.Shell").SpecialFolders(16)
On Error Resume Next

' Get the collection of selected objects.
Set objSelection = objOL.ActiveExplorer.Selection

' Set the Attachment folder.
' You could check for the existance of the folder and 
' prompt the user if you want
sFolderpath = sFolderpath & "\ICSFiles\"

' Check each selected item for attachments.
'''''''''''''''''''''''''''''''
' Remove loop if used in script (you call script with the item)
For Each objitem In objSelection
'''''''''''''''''''''''''''''''''''''
	' Remove this check if running as a script
	' This code only strips attachments from mail items.
	If objitem.Class = olMail Then
	'''''''''''''''''''''''''''''''''''
		' Get the Attachments collection of the item.

		Set objAttachments = objitem.Attachments
		lCount = objAttachments.Count

		If lCount > 0 Then
			' Since we are removing things from an array, remove
			' from the end going to the beginning
			For i = lCount To 1 Step -1

				' Save attachment before deleting from item.
				sFile = objAttachments.Item(i).FileName
				' only looking for ICS file attachments
				If Right(sFile, 3) = "ics" Then
					' Combine with the path to the Temp folder.
					sFile = sFolderpath & sFile

					' Save the attachment as a file.
					objAttachments.Item(i).SaveAsFile sFile
                                        ' remove attachment
					objAttachments.Item(i).Delete

					'write the save as path to a string to add to the message
					If objitem.BodyFormat <> olFormatHTML Then
						sDeletedFiles = sDeletedFiles & vbCrLf & "<file://" & sFile & ">"
					Else
						sDeletedFiles = sDeletedFiles & "<br>" & "<a href='file://" & sFile & "'>" & sFile & "</a>"
				End If
			Next i
		End If

		' Adds the filename string to the message body and save it
		If objitem.BodyFormat <> olFormatHTML Then
			 objitem.Body = objitem.Body & vbCrLf & "The file(s) were saved to " & sDeletedFiles
		 Else
			 objitem.HTMLBody = objitem.HTMLBody & "<p>" & "The file(s) were saved to " & sDeletedFiles & "</p>"
		 End If
		 ' save the modified email with link to the detached file
		 objitem.Save
	''''''''''''''''''''
	' remove if running as a script
   End If
   '''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''
' remove loop if running as a script
Next
''''''''''''''''''''''''''''''''
ExitSub:

Set objAttachments = Nothing
Set objitem = Nothing
Set objSelection = Nothing
Set objOL = Nothing
End Sub

(Sorry for the screwed up code listing but the file link tags are being improperly interpreted by the plugin I use).

This macro was designed to run on Outlook 2007. You will most likely have to change the security settings in Outlook to have it run (and sign the macro or whatever). There is minimal (none) error checking in this code but then I’m not going to do it all for you.

]]>