≡ Menu

Creating Calendar Items with PowerShell and the EWS Managed API

I have a bunch of monitoring scripts that send me e-mail notifications using the Send-MailMessage cmdlet. This has worked well for a while because it reminds me that I need to check on something. The problem I’ve had lately is that I have tons of e-mail every morning, and often times I’ll mark these messages as read, and then forget about them later in the day. So one of the things I’ve been doing lately is adding these notifications as calendar items in my mailbox. This has worked even better because I can set the start date later in the day and then Outlook and my mobile phone will remind me when I need to check on something.

This is the PowerShell function I wrote that uses the EWS Managed API to create calendar items in an Exchange mailbox:

function New-CalendarItem {
    [CmdletBinding()]
    param(
        [Parameter(Position=1, Mandatory=$true)]
        $Subject,
        [Parameter(Position=2, Mandatory=$true)]
        $Body, 
        [Parameter(Position=3, Mandatory=$true)]
        $Start, 
        [Parameter(Position=4, Mandatory=$true)]
        $End, 
        [Parameter(Position=5, Mandatory=$false)]
        $RequiredAttendees, 
        [Parameter(Position=6, Mandatory=$false)]
        $OptionalAttendees, 
        [Parameter(Position=7, Mandatory=$false)]
        $Location,
        [Parameter(Position=8, Mandatory=$false)]
        $Impersonate
        )

    Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
    $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
    $user = [ADSI]"LDAP://<SID=$sid>"        
    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList ([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
    $service.AutodiscoverUrl($user.Properties.mail)

    if($Impersonate) {
        $ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList ([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress),$Impersonate 
        $service.ImpersonatedUserId = $ImpersonatedUserId   
    }

    $appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $service
    $appointment.Subject = $Subject
    $appointment.Body = $Body
    $appointment.Start = $Start
    $appointment.End = $End 
    
    if($RequiredAttendees) {$RequiredAttendees | %{[void]$appointment.RequiredAttendees.Add($_)}}
    if($OptionalAttendees) {$OptionalAttendees | %{[void]$appointment.RequiredAttendees.Add($_)}}
    if($Location) {$appointment.Location = $Location}
    
    $appointment.Save([Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendToAllAndSaveCopy)
}

After you’ve added the function to your PowerShell session, creating a calendar item is easy. For example, this will create a calendar item that starts in six hours:

$start = (Get-Date).AddHours(6)
$end = $start.AddHours(1)

New-CalendarItem -Subject "Check Disk Free Space" -Body "Make sure that servers are not running out of disk space" -RequiredAttendees helpdesk@contoso.com -Start $start -End $end

In this example, I’ve used the optional parameter RequiredAttendees to add the helpdesk mailbox as an attendee to the appointment. You can also use this function with the Impersonate parameter to create calendar items in another users mailbox. You’ll need to be assigned the application impersonation RBAC role in order for this to work:

New-CalendarItem -Subject "Reboot Server" -Body "Reboot EXCH-SRV01 server after 5PM today" -Start $start -End $end -Impersonate sysadmin@contoso.com

If you want to create calendar items in multiple mailboxes, loop through a collection with the foreach-object cmdlet and run the function for each user. For example, to create an appointment in the mailbox of each member of the ITSupport distribution group:

Get-DistributionGroupMember ITSupport | Foreach-Object{New-CalendarItem -Subject "Install Hotfixes" -Body "Start patching servers after 5PM today" -Start $start -End $end -Impersonate $_.PrimarySMTPAddress}

I’ve actually found this to be quite useful. I’ve implemented some basic functionality here, but you can extend this function to do even more. Check out the available members in the Appointment class for more details.

36 comments… add one

  • Yps May 5, 2011, 4:45 am

    Hi, got this error:
    Exception setting “Start”: “Cannot convert value “” to type “System.DateTime”. Error: “String was not recognized as a valid DateTime.””
    At C:\Powershell\Functions\New-calenderitem.ps1:36 char:18
    + $appointment. <<<< Start = $Start
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : PropertyAssignmentException

    Exception setting "End": "Cannot convert value "" to type "System.DateTime". Error: "String was not recognized as a valid DateTime.""
    At C:\Powershell\Functions\New-calenderitem.ps1:37 char:18
    + $appointment. <<<(Get-Date).AddHours(6)

    den 5 maj 2011 19:42:13

    Is it possible to change it easy?
    Thanks, Magnus

  • Mike Pfeiffer May 5, 2011, 11:19 am

    Hi, can you post the command you are trying to run?

  • Yps May 6, 2011, 12:15 am

    [PS] C:\Windows\system32>$start=”den 8 maj 2011 06:00:00″
    [PS] C:\Windows\system32>$end=”den 8 maj 2011 19:00:00″
    [PS] C:\Windows\system32>New-CalendarItem -Subject “Check Disk Free Space” -Body “Make sure that servers are not running out of disk space” -RequiredAttendees xxxxx@xxxx.com -Start $start -End
    Exception setting “Start”: “Cannot convert value “den 8 maj 2011 06:00:00″ to type “System.DateTime”. Error: “The string was not recognized as a valid DateTime. There is a unknown word starting at inde
    At C:\Powershell\Functions\New-calenderitem.ps1:36 char:18
    + $appointment. <<<< Start = $Start
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : PropertyAssignmentException

    Exception setting "End": "Cannot convert value "den 8 maj 2011 19:00:00" to type "System.DateTime". Error: "The string was not recognized as a valid DateTime. There is a unknown word starting at index
    At C:\Powershell\Functions\New-calenderitem.ps1:37 char:18
    + $appointment. <<<get-date

    den 6 maj 2011 09:12:40

    [PS] C:\Windows\system32>

    But this works fine:
    [PS] C:\Windows\system32>$start = (Get-Date).AddHours(6)
    [PS] C:\Windows\system32>$end = $start.AddHours(1)
    [PS] C:\Windows\system32>
    [PS] C:\Windows\system32>New-CalendarItem -Subject “Check Disk Free Space” -Body “Make sure that servers are not running out of disk space” -RequiredAttendees magnus.tengmo@unit4.com -Start $start -End $en
    [PS] C:\Windows\system32>

    Maybe it´s not possible to enter time manually?

  • Mike Pfeiffer May 6, 2011, 4:33 pm

    try something like this:

    [datetime]$start = “Friday, May 06, 2011 4:30:00 PM”
    [datetime]$end = “Friday, May 06, 2011 5:30:00 PM”

    or…

    $start = Get-Date “Friday, May 06, 2011 4:30:00 PM”
    $end = Get-Date “Friday, May 06, 2011 5:30:00 PM”

    Then you can assign $start and $end to the -Start and -End parameters.

    The Start and End properties of the appointment class require a DateTime object, and I didn’t write the function to convert strings to that data type…although I may update it later to do this :)

  • Robert June 14, 2011, 5:52 pm

    Hi Mike,

    I have a script which creates appointments in a users mailbox using EWS Managed API 1.1. Everything works ok except I cannot figure out how to setup a recurrence of the meeting to occurr every day in a particular date range.
    A snippet of my code is below:
    $CALENDAR = New-Object Microsoft.Exchange.WebServices.Data.Appointment($EXCHANGE_SERVICE)
    $CALENDAR.Subject = $SUBJECT
    $CALENDAR.Body = $BODY
    $CALENDAR.ICalUid = $TRANS_ID
    $CALENDAR.Start = $APPOINTMENT_START
    $CALENDAR.End = $APPOINTMENT_END
    $CALENDAR.IsAllDayEvent = $False
    $CALENDAR.LegacyFreeBusyStatus = “Busy”
    $CALENDAR.IsReminderSet = $False
    $CALENDAR.Save($MAILBOX_FOLDER)

    Can you please provide me the correct syntax for recurrence in powershell? All the examples on MSDN are in C# and I cannot for the life of me figure out how to do it in PowerShell.
    Cheers.
    Rob

  • Mike Pfeiffer June 17, 2011, 10:53 am

    Hi Rob, sorry for the late reply…busy week

    Here’s an example of a recurring meeting that runs every Friday for a year:

    Add-Type -Path ‘C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll’
    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
    $service.AutodiscoverUrl(‘administrator@uss.local’)

    $appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $service

    $Start = Get-Date
    $End = $Start.AddHours(1)

    $appointment.Subject = ‘Sales Meeting’
    $appointment.Body = ‘See ya there’
    $appointment.Start = $Start
    $appointment.End = $End
    $appointment.RequiredAttendees.Add(‘tdawson@uss.local’)
    $appointment.Location = ‘Corporate’

    $Recurrence = New-Object Microsoft.Exchange.WebServices.Data.Recurrence+WeeklyPattern
    $Recurrence.StartDate = $Start
    $Recurrence.NumberOfOccurrences = 52
    $Recurrence.DaysOfTheWeek.Add(‘Friday’)

    $appointment.Recurrence = $Recurrence
    $appointment.Save([Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendToAllAndSaveCopy)

  • Jay Omayan December 12, 2011, 12:00 am

    $CALENDAR.IsAllDayEvent = $False

    This command doesnt work on mine.. it doesnt even give an error…
    I just added this line in Mike’s script but it doesnt work…

  • Jill September 13, 2011, 9:25 am

    Can you update a Calendar appointment in Powershell? Like update the time or location?

  • Mike Pfeiffer September 13, 2011, 9:57 am

    Yes, you can. Here are some code samples in C#, but it could be done in PowerShell:

    http://msdn.microsoft.com/en-us/library/dd633641%28v=EXCHG.80%29.aspx

    If you need help writing a script, let me know more about the requirements, the version of Exchange you are running, etc.

  • Jay Omayan December 11, 2011, 9:49 pm

    Hi Mike,

    I’ve been using your script for quite a while and it works great.
    Do you have a script that can update or delete the calendar item using powershell?

    if you have one, it would be a lifesaver for me..

    Thanks

    -Jay

  • Mike Pfeiffer December 11, 2011, 9:58 pm

    I don’t have anything laying around for that…but I will add this to my list and post an update for it later.

  • Aaron Sims January 15, 2012, 3:09 pm

    Hi Mike, Did you ever come up with a way of updating/deleting a calendar item?

    Cheers
    Aaron

  • Jill September 13, 2011, 11:15 am

    Actually, I am very new to PowerShell so I don’t know much about using Exchange. I’ve only done a couple small scripts using the AD module. How do you retrieve the calendar appointment using PowerShell? And what I really need to do is loop through a list of users from a Distribution Group, and update a certain appointment on their calendars with new information (usually start and end time or location…but possibly date as well.) Our Exchange server is Exchange 2003 (we are testing out 2010 right now and haven’t rolled it out to all users yet). Users are on Outlook 2010.

  • Mike Pfeiffer September 14, 2011, 3:07 pm

    In order to use EWS, you need to be running Exchange 2007 or Exchange 2010.

  • Jill September 13, 2011, 11:17 am

    Sorry, update to the above.

    I need to loop through the users in the DL and check to see if that appointment exists on their calendar. If it does, I need to do the update.

    Maybe it would be better to do this in C#. But still curious how to do so in PowerShell.

  • Paul Yearron October 7, 2011, 8:03 am

    Hi, I am using Exchange 2010 and get the following after I enter the New-Calendar Item Line.
    Exception calling “Save” with “1″ argument(s): “Exchange Server doesn’t support the requested version.”
    At line:38 char:22
    + $appointment.Save <<<< ([Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendToAllAndSaveCopy)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

  • Mike Pfeiffer October 14, 2011, 12:58 pm

    Hi Paul,

    Looks like you’re not running SP1 huh? Just change the Exchange version in the script to Exchange2010 instead of Exchange2010_SP1. I haven’t actually tried this code against an RTM server, so YMMV. Regardless, I’d upgrade to SP1 ASAP since RTM is no longer supported.

    http://support.microsoft.com/kb/2615653/

    Thanks,
    Mike

  • Paul Yearron October 19, 2011, 3:59 am

    Yes this works on SP1, thanks.

  • Ken Chiu November 11, 2011, 1:38 pm

    Hi Mike,

    Does this works on Exchange 2007? If so, I only need to change the path reference and the version?

    Thanks,
    Ken

  • Derek Hanson March 21, 2013, 11:27 am

    Hello Ken,

    The EWS Managed API works on Exchange 2007 SP1 and above.

    Thanks,
    Derek

  • Scott Merry January 13, 2012, 3:14 pm

    Hi Mike,

    We are trying to create custom Holidays for our company. How can we create All Day Events instead of Appointments? We would like for them to show as Free on the calendar and have the Reminder set to none. Any help would be appreciated.

    Thanks

    Scott

  • Behnam February 1, 2012, 7:10 am

    when run the example I receive followign error : please help me to slve it

    Exception calling “AutodiscoverUrl” with “1″ argument(s): “A valid SMTP address must
    At d:\test.ps1:26 char:29
    + $service.AutodiscoverUrl <<<< ($webmail.mapnaturbine.com)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

    Exception calling "Save" with "1" argument(s): "The Url property on the ExchangeServ
    At d:\test.ps1:43 char:22
    + $appointment.Save <<<< ([Microsoft.Exchange.WebServices.Data.SendInvitationsMo
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

  • sweyland February 22, 2012, 7:04 am

    Hi Mike,
    Works great but having a problem with calendar body not recognising line feeds, I cannot get it to work. :(
    when creating an appointment like so ..
    New-CalendarItem -Subject $Subject -Body “line 1 `r`n line2″ -Location $Location -Start $start -End $end -Impersonate $emailAddress

    The body of the calendar is just a single line “line1 line2″
    The same format using built in smtp.Send works fine.

  • Andre March 9, 2012, 7:25 pm

    Hi Guys,

    I am trying to get EWS scripts to work for 2 days now, but whatever I do I end up with:

    Exception calling “Save” with “1″ argument(s): “The request failed. The underlying connection was closed: An unexpected error occurred on a send.”

    if I try https://cas/ews/exchange.asmx in IE I get a warning that only secure content will be displayed and I have to confirm before it shows the xml.

    Any idea where to start trouble shooting?

    Thanks,
    Andre

  • Joe N March 26, 2012, 8:34 am

    Hey,
    I am having the same problem as Andre. But my “The request failed. The underlying connection was closed: An unexpected error occurred on a send.” error is on a BIND command in a script using EWS.

    This script worked fine a few weeks ago and now I get this. Been searching and reading…. I did find this on the Microsoft site, but now sure what to make of it. http://support.microsoft.com/kb/915599

    Has anyone fixed this issue?

    Thanks in advance!

  • ivo April 3, 2012, 1:11 pm

    Got an error:
    Exception calling “AutodiscoverUrl” with “1″ argument(s): “The Autodiscover service couldn’t be located.”

    when using the
    $service.AutodiscoverUrl(`administrator@domain.local’)

    Any idea how to fix this?

  • Mike Pfeiffer April 17, 2012, 3:12 pm

    I’ve seen this when the certificate on the CAS was untrusted/self signed. Make sure its trusted on the machine you’re running the code from. Another option is to add this command inside the function to ignore the errors:

    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

  • Erik Snijder July 9, 2012, 6:19 am

    Hello Mike,

    Thank you for your work, very interesting!
    Can you confirm that these commands also work within an Exchange online (Office365) environment?

    Regards,

    Erik

  • Mike Pfeiffer July 11, 2012, 6:27 pm

    It’s possible but the code would need to change a bit. Is this a hybrid or fully hosted? The main piece that would need to change would be the call to the autodiscover method. For example:

    $service.AutodiscoverUrl(‘joe@tenant.onmicrosoft.com’, {$true})

    You’ll need to modify the function to accommodate this…let me know if you need help.

  • warren September 7, 2012, 4:51 am

    Mike,

    Anyway we can make this an all day event, i have edited the script to include all day event and run this:
    New-CalendarItem -Subject “Draft Invoices to Accounts’” -Body “This event as been automatically been added by SLR Accounts” -Start “Saturday, September 8, 2012” -IsAllDayEvent =true -ReminderMinutesBeforeStart 1440 -Impersonate wmills@slrconsulting.com
    it works but does not amke it and all day event.

    Any pointers will b e appreciated.

    Warren

  • Mike Pfeiffer September 11, 2012, 1:02 pm

    Try adding this code to the function to make the appointment an all day event:

    [powershell]
    $appointment.IsAllDayEvent = $true
    $appointment.Categories.Add(‘Holiday’)
    $appointment.Location = ‘United States’
    $appointment.LegacyFreeBusyStatus = [Microsoft.Exchange.WebServices.Data.LegacyFreeBusyStatus]::Free
    [/powershell]

  • Joe Daly March 4, 2013, 2:10 pm

    I downloaded your PS1 file and was able to successfully create an appointment under my default calendar using the PS1 script provided and the new-calendaritem example.

    I do have a question though. If I have a second calendar that I created called say Calendar2 is there a way using this script to write events to that calendar instead of the default one?

    Im assuming this would just be another parameter that needed to be entered but I cant seem to find where you specific calendar location.

    Any help is appreciated.

    Thanks

  • amstuartc October 22, 2013, 9:44 am

    Hi Mike
    I have a query concerning your example and could it be altered to allow booking Calendar appointments from a .CSV file where the meeting Organizer would be the impersonatedID but I would need to send the meeting request to a different “Room” mailbox as opposed to a default calendar that has auto booking/confirmation setup so as to send a reply to the Organizer and store a copy in the Room Calendar, but I can’t seem to find the options to set this, can you help please?
    I have a CSV with startdate, starttime, enddate, endtime, subject, body, ImpersonateId and RoomId.
    Thanks

  • Tim November 21, 2013, 1:42 pm

    Is there any way to suppress the RequiredAttendees getting the Accept or Reject email? We have a need to send out a meeting to user calendars, using impersonation, but want them to automatically be accepted, and for the user to NOT get the email asking them to take action. Is that at all possible?

  • Laurent BERARD March 28, 2014, 8:48 am

    Hi Mike,

    Thanks a lot for your great job !
    first: sorry for my english , i’m french ,
    I was looking for a solution to import vacations, absences, … of employees of my company in Exchange, and i found your article :-)

    I can tell you that it works fine on MS Exchange 2013

    just modify this line :
    Add-Type -Path “C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.WebServices.dll”

    Regards,
    Laurent
    laurent.berard@gmail.com

  • Mike Pfeiffer May 13, 2014, 2:32 pm

    Thanks Laurent for the update.

Leave a Comment