-->
顯示包含「Task Scheduler」標籤的文章。顯示所有文章
顯示包含「Task Scheduler」標籤的文章。顯示所有文章

2015年10月26日星期一

Task Scheduler+VBA: Send an email via SMTP using VB script

Task Scheduler provides "Send an e-mail" function, but it does not work with authentication. To solve the problem, I created a script with arguments for sending an email using "Start a program" action instead of "Send an e-mail" in Task Scheduler.

1) Copy and save below script in your computer. For example: save file in D:\script\ and named "SendEmail.vbs".
'==============================================================================
'=== Send Email using VBS Script
'===    arg1: Email Subject
'===    arg2: Email From
'===    arg3: Email To
'===    arg4: Email Text Body
'==============================================================================

SET args = WScript.Arguments
SET objEmail = CreateObject("CDO.Message")
SET objEmlConfig = objEmail.Configuration

DIM emlSubject
DIM emlFrom
DIM emlTo
DIM emlBody

emlSubject = args.Item(0)
emlFrom = args.Item(1)
emlTo = args.Item(2)
emlBody = args.Item(3)


WITH objEmlConfig.Fields
    .Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2      ' 2 = using SMTP
    .Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.server"      ' your smtp server id
    '.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
    .Update
END WITH

WITH objEmail
    .Subject = emlSubject
    .From = emlFrom
    .To = emlTo

    .TextBody = emlBody
END WITH

' send email
objEmail.Send

SET objEmlConfig = nothing
SET objEmail = nothing
Remember to change the "smtp.server" as your SMTP Server.

2) Create a Task in Task Scheduler.
2.1) In General page, input the name of task.
2.2) In Triggers page, create a new trigger and set "Begin the task" is "On a schedule".
2.3) In Actions page, create a new action and set "Action" is "Start a program".
2.3.1) Set "Program/script" is "D:\script\SendEmail.vbs"
2.3.2) Set "Add arguments:" is "send email from vbs" "sendfrom" "sendto" "email_body". Remember to change the "sendfrom" and "sendto" as your email address.
2.3.3) Set "Start in" is "D:\script\".
2.4) Press OK to save.
schaction

You can try to run the task in Task Scheduler Library.

Outlook+VBA: How to delete older deleted email after n days automatically on schedule?

Here's coding is only for selecting a specific folder such as "Deleted Items" and check the date whether is older than n days, for example is 14 days, then delete it permanently.

Reference, if you don't know : How to create VBA function, Outlook rule and enable macros in Outlook?

1) Press Alt+F11 to open VBA to create a function on Outlook.

Example 1:
A simple way to delete all type of emails which are under “Deleted Items" folder and the Last Modifcation Time (DateTime) older than 14 days.
Sub CleanupDeletedEmail(Item As Outlook.MailItem)
    Dim DelItems As Outlook.Items
    Dim OlderDay As Integer
    
    OlderDay = 14

    Set DelItems = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderDeletedItems).Items
    
    For i = DelItems.Count To 1 Step -1
       If DateDiff("d", DelItems.Item(i).LastModificationTime, Date) >= OlderDay Then
            DelItems.Item(i).Delete
       End If
    Next

    Set DelItems = Nothing
End Sub
Tips:
1. You can also change the folder from Deleted Items “olFolderDeletedItems" to another such as Inbox “olFolderInbox".
2. As email that received will include non-email type, for example delivery email error that sent by outlook server or task, which does not have SentOn date, so using “LastModificationTime" instead of “SentOn".


Example 2:
Sending an email if there is any error found during deleting. Besides, if item type is email then check the SentOn date otherwise using LastModificationTime.
Sub CleanupDeletedEmail(Item As Outlook.MailItem)
    On Error GoTo ErrHandler
    
    Dim DelItems As Outlook.Items
    Dim OlderDay As Integer
    Dim IsDel As Boolean
    Dim sRptToEmailAddr As String
    
    OlderDay = 14
    sRptToEmailAddr = "name@domain.com"    

    Set DelItems = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderDeletedItems).Items
    
    For i = DelItems.Count To 1 Step -1
        IsDel = False    

        If DelItems.Item(i).Class = olMail Then
            If DateDiff("d", DelItems.Item(i).SentOn, Date) >= OlderDay Then
                IsDel = True
            End If
        ElseIf DateDiff("d", DelItems.Item(i).LastModificationTime, Date) >= OlderDay Then
            IsDel = True
        End If
        
        If IsDel = True Then DelItems.Item(i).Delete
    Next
    
GoTo Finally
ErrHandler:
    Call CreateNewMessage("Error: " + Item.Subject, sRptToEmailAddr, Err.Description)
Finally:
    Set DelItems = Nothing
End Sub

Sub CreateNewMessage(pSubject As String, pTo As String, pBody As String)
    Dim objMsg As MailItem    
    Set objMsg = CreateItem(olMailItem)
    With objMsg
        .Subject = pSubject
        .To = pTo
        .Body = pBody
        
        .Send
    End With

    Set objMsg = Nothing
End Sub

2) Save and exit VBA.

3) Create a new rule on Outlook.
3.1) Start from a blank rule: Select "Apply rule on messages I receive" -> press Next
3.2) Which condition(s) do you want to check? Select "with specific words in the subject"
3.3) Use mouse to click "specific words" in "Edit the rule description" box, and input "Call Cleanup Deleted Email". -> press "Add" -> press "OK".
3.4) What do you want to do with the message? Select "delete it" and "run a script".
3.5) Use mouse to click "run a script" in "Edit the rule description" box, and select "Project1.ThisOutlookSession.CleanupDeletedEmail". -> press Next.
3.6) Press Next to ignore "Are there any exceptions?".
3.7) Assign a name for this rule: Call Cleanup Deleted Email.
3.8) Press Finished.

rule

The action of this rule: When new email arrived which the subject is "Call Cleanup Deleted Email" then delete this email and run the script "CleanupDeleteEmail".

You can now to send an email with the subject is "Call Cleanup Deleted Email" to this account for checking whether the rule can call the script and delete the email which the date is older than 14 days correctly.

Using below tutorial link to create a Task Scheduler and send "Call Cleanup Deleted Email" email periodically.
Task Scheduler: Another way to send an email via SMTP using script

Below is an example to kick-off the task on 12:05am everyday. There are many options such as Daily, Weekly, Monthly or Repeating for you selection, so you can assign what you want in Trigger.
task

2015年9月22日星期二

C#.net: How to get a default instance of Outlook and run in Task Scheduler?

The following example represents a function to open and log on to Outlook for getting emails. You can also reference this website "How to: Get and log on to an Instance of Outlook" which is provided by Microsoft. Based on the example of the Microsoft's website provided which occur the following errors in "Marshal.GetActiveObject("Outlook.Application") as Outlook.Application", and when add the program in Task Scheduler running.

1) System.Runtime.InteropServices.COMException (0x800401E3)...
2) 0x800401e3 "Operation unavailable"
3) Object reference not set to an instance of an object
4) System.Runtime.InteropServices.COMException (0x80080005): Retrieving the COM class factory for component with CLSID {0006F03A-0000-0000-C000-000000000046} failed due to the following error: 80080005.
5) Creating an instance of the COM component with CLSID {0006F03A-0000-0000-C000-000000000046} from the IClassFactory failed due to the following error: 80010001.

As GetActiveObject function just work on debug mode. This forum "Marshal.GetActiveObject(“Outlook.Application”) throws MK_E_UNAVAILABLE when debugging with elevated privileges" mentioned that the reason is different user level running which Visual Studio runs as Administrator and Outlook was opened as user. I tried to change Privilege Level of my program and Outlook as "Run this program as an administrator", but it will prompt a User Account Control message box in every start, although I can adjust notification level to lower, but I don't want to do that. Besides, my program file will automatically deleted and copied from server if there is any file changed, so it is not a good practice.

Basically, Outlook.NameSpace function works fine if you don't mind running Outlook in background. Because I have to monitor the status of Outlook whether already got and deleted emails, so the function is also not work for me.

The example code of Microsoft website above describes getting a current instance of Outlook which need use GetActiveObject function, but it is not totally correct. If Outlook application is installed and create an object (Obj = new Outlook.Application) in program, the program can automatically find and assign the current instance of Outlook to the object, therefore I use create an object instead of use GetActiveObject function.

Here's my example.

Minimum requirement for my example:
1) Microsoft Outlook application must be installed in the computer.
2) If the computer has multiple outlook's profiles installed, you should define one of outlook's profile as default in Mail setting otherwise Outlook will prompt a profile screen for selection when open in every time. How to set default: Control Panel->Mail->General->select "Always use this profile".
3) Enable Programmatic Access in Microsoft Outlook. How to: File->Outlook->Trust Center->Trust Center Settings->Programmatic Access->select "Never warn me about suspicious activity".

Pesudo-code
Start a program and call the below function (example code below).
1) Detect whether Outlook is opened.
1.1) If yes then close Outlook process with hidden and shown.
1.2) If no then Open Outlook.
1.3) Waiting up to 5 mins If cannot found any Outlook process then raise error and exit.
2) Create an new object. e.g. oOLApp = new Outlook.Application().
3) Double Check whether Outlook is opened with profile successfully otherwise raise error and exit.
4) Detect Outlook connection status whether already connected to Microsoft Exchange.
4.1) Waiting up to 5 mins if the status is not connected then raise error and exit.
5) Waiting Sync is completed.

Here's the code
using Outlook = Microsoft.Office.Interop.Outlook; 

Outlook.Application oOLApp = null;

private void form1_Load(object sender, EventArgs e)
{
    try
    {
        this.FindnOpenOutlook();
  
        while (this.GetEmail.Count >0)
        {
      // ......
        }
    }
 catch (Exception ex)
    {
     MessageBox.Show(ex.Message); 
    }
}

private void FindnOpenOutlook()
{
    // 1 & 1.1) Find and kill all outlook process.
    foreach (Process p in Process.GetProcesses().Where(x => x.ProcessName.ToString().ToUpper() == "OUTLOOK"))
    {
        p.Kill();
        p.WaitForExit();
    }
    
    // 1.2) Open Outlook application.
    ProcessStartInfo sinfo = new ProcessStartInfo();
    sinfo.UseShellExecute = true;
    sinfo.WindowStyle = ProcessWindowStyle.Normal;
    sinfo.FileName = "outlook.exe";
    
    using (Process exeProcess = Process.Start(sinfo)) { exeProcess.WaitForInputIdle(); }
    sinfo = null;
    
    // 1.3) Waiting up to 5 mins if cannot found Outlook process then raise error.
    dtLast = DateTime.Now;
    while (Process.GetProcessesByName("OUTLOOK").Count() == 0)
    {
        Thread.Sleep(500);
        if (DateTime.Now > dtLast.AddMinutes(5)) { throw new Exception("Cannot open Outlook application."); }
    }
    
    // 2) Create an new object
    // If Outlook application found then oOLApp.Session.CurrentProfileName is not null otherwise oOLApp is null.
    oOLApp = new Outlook.Application();        //instead of oOLApp = (Outlook.Application)Marshal.GetActiveObject("Outlook.Application");
    
    // 3) Check whether Outlook application is opened with outlook profile successfully.
    if ((oOLApp.Session.CurrentProfileName == null) || (oOLApp.Session.CurrentProfileName == "")) { throw new Exception("Cannot find Outlook application"); }
    
    // 4 & 4.1) Detect Outlook connection status whether already connected to Microsoft Exchange.
    // If not using Cached Exchange Mode, then check olOnline instead of olCachedConnectedFull.
    dtLast = DateTime.Now;
    while (oOLApp.Session.ExchangeConnectionMode != Outlook.OlExchangeConnectionMode.olCachedConnectedFull)
    {
        Thread.Sleep(500);
        if (DateTime.Now > dtLast.AddMinutes(5)) { throw new Exception("Exchange Connection failed. "); }
    }
    
    // 5) Waiting Sync is completed.
    syncObjs = oOLApp.Session.SyncObjects;
    for (int i = 1; syncObjs.Count >= i; i++)
    {
        syncAObj = syncObjs[i];
        syncAObj.SyncStart += syncObj_SyncStart;
        syncAObj.SyncEnd += syncObj_SyncEnd;
    
        if (syncAObj != null)
        {
            IsSyncFinished = false;
            syncAObj.Start();
    
            while (IsSyncFinished == false) { Thread.Sleep(500); }        // waiting sync completed
        }
        System.Runtime.InteropServices.Marshal.ReleaseComObject(syncAObj);
    }
}
   

Close Outlook function
public void CloseMSOutlook()
{
    try
    {
        if (oOLApp != null)
        {
            oOLApp.Session.Logoff();
            oOLApp.Quit();
        }
    }
    catch (Exception ex)
    { throw ex; }
    finally
    {
        if (oOLApp != null) { System.Runtime.InteropServices.Marshal.ReleaseComObject(oOLApp); }

        GC.Collect();
        GC.WaitForPendingFinalizers();

        foreach (Process p in Process.GetProcesses().Where(x => x.ProcessName.ToString().ToUpper() == "OUTLOOK"))
        {
            p.Kill(); p.WaitForExit(); 
        }
    };
} 

Coding in Program.cs
Adding below coding in a Program.cs of C#.net to check whether the application is running, if not found then run the application otherwise ignore.
{
    static class Program
    {
        private static Mutex mutexOLEmail = null;

        [STAThread]
        static void Main()
        {
            string sAppExePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

            try
            {
                mutexOLEmail = new Mutex(true, "{myOLEmailPgm} " + sAppExePath.Replace("\\", ""));
                if (mutexOLEmail.WaitOne(TimeSpan.Zero, false))
                {
                    Application.Run(form1);
                }
            }
            catch (Exception ex)
            { MessageBox.Show(ex.Message); }
            finally
            {
                if (mutexOLEmail != null) { mutexOLEmail.Close(); }
                mutexOLEmail = null;
            }
        }
    }
 } 
The above coding will prevent to re-open the program, so now you can add a trigger, for example runs the program at 9am everyday or every 5 minutes, in Task Scheduler.