Tag Archives: automation

MacRuby – Create iCal event with alarm from Ruby

I’ve had to create some very similar events in my calendar, but they did not match a valid recurrence pattern. As I find it quite painful to create multiple iCal events I thought I maybe should try out MacRuby to automate the process as far as I could. As the resources for this topic are rare on the net I thought I could share the basics. I’ll explain the details below the coding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/local/bin/macruby
framework 'calendarstore'
 
defStore = CalCalendarStore.defaultCalendarStore
 
puts "Select the calendar:"
defStore.calendars.each_with_index do |cal,i|
  puts "\t#{i}. #{cal.type}\t#{cal.title}"
end
 
puts "Type in the number for the calendar you wish to use:"
cal_index = gets.to_i
 
puts "Creating an event..."
 
# setting up the basics
e = CalEvent.new
e.calendar = defStore.calendars[cal_index]
e.title = 'Just a macruby test'
e.startDate = Time.now + 3600
e.endDate = Time.now + 7200
 
# creating the alarm
a = CalAlarm.alarm
a.action = CalAlarmActionSound
a.sound = "Basso"
a.relativeTrigger = -60 * 60 # negative seconds
e.addAlarm a
 
# save the event
errorPointer = Pointer.new :object
success = defStore.saveEvent(e, span:CalSpanThisEvent, error:errorPointer)
puts "Error: #{errorPointer[0].userInfo}"if !success
 
puts "Finished calendar macruby test"

In the first step we have to get the default CalCalendarStore. This object contains all calendars we have configured in iCal.

The next few lines query the calendar which the test event should be put in.

From line 17. on the CalEvent object is initialized with several values. Note, that the chosen calendar is a property of the event. In this test case the event is created at now + 2hours end in now + 4hours. You can see that Ruby data types blend wonderfully into Objective-C types, as usually startDate and endDate would need a NSDate object.

For the perfect topping an alarm is created from line 23. on. The CalAlarm object is initialized with several values and then added to the event. You could go forward and start to add more reminders, as e.addAlarm a can be called multiple times.

Until now the stuff is not yet saved. Apple designed the library so that the CalCalendarStore object is responsible to save the event. So we have to call defStore.saveEvent, with the event object itself, the span (relevant for recurring events), and an error pointer.

If you did everything correct, you should see a new event in your iCal!

Ruby on Windows: OLE automation for Outlook – Shared calendars

Ruby has a nice library for OLE automation, win32ole. I used this library to get appointments from shared outlook (exchange) calendars. The scripts are testen on Windows Vista with Outlook 2003. This is how it works:

outlook_reader.rb

require 'win32ole'
outlook = WIN32OLE.new('Outlook.Application')
mapi = outlook.GetNameSpace('MAPI')

# this is explained later
Thread.new do
  system('outlook_clicker.exe')
end

# create a recipient and resolve him
myRecipient = mapi.CreateRecipient("John Doe")
myRecipient.Resolve

# get the shared calendar of the recipient
if myRecipient.Resolved
  calendar = mapi.GetSharedDefaultFolder(myRecipient, 9)
end

# write the calendar data to the output
if calendar
  calendar.Items.each do |item|
    puts "#{item.Start} - #{item.End}: #{item.Subject}"
  end
end

Outlook has a mechanism which should protect the outlook data from unauthorized access, e.g. viruses and trojans. Everytime you want to access the data from outlook a popup opens which asks you, if the access should be granted. In case of automation this is very bad, because you usually don’t want to click anything if you automate a process.

So here the solution. I’ve created a ruby script which clicks the dialog for the user. Then I converted it into an executeable using RubyScript2exe (Ruby code gets compiled).

outlook_clicker.rb

require 'win32ole'

# new shell object
wsh = WIN32OLE.new('Wscript.Shell')

# for timeout management
time_out = 0
time_out_max = 10
interval = 0.01

# try to activate the window with the Title 'Microsoft Office Outlook'
while !wsh.AppActivate('Microsoft Office Outlook') do
  time_out = time_out + interval
  sleep(interval)
  if time_out > time_out_max
    Process.exit!
  end
end

# if the popup exists and the timeout is not reached some
# keys get send to the popup
wsh.SendKeys("{TAB}")
wsh.SendKeys("{TAB}")
wsh.SendKeys(" ") # this is "space"
wsh.SendKeys("{TAB}")
wsh.SendKeys("{TAB}")
wsh.SendKeys("{ENTER}")

To compile this ruby file into an executeable rubyscript2exe can be used. To install it you can use Ruby Gems (command prompt):

gem install rubyscript2exe

After installing you can open a command prompt, change to the directory your scripts are in and compile the clicker using the following command

ruby rubyscript2exe.rb outlook_clicker.rb

The executable will be created in the same directory. In the file outlook_reader.rb the following lines call the outlook_clicker.exe:

Thread.new do
  system('outlook_clicker.exe')
end

In this three lines a new thread is created which calls the outlook_clicker.exe using the system method. Basically we reached that both scripts are executed in parallel.

If you execute the outlook_reader.rb you can see that the popup shortly opens and then automatically gets closed.

Recommend reading: http://rubyonwindows.blogspot.com/