Running Windows Mobile apps from a PC using RAPI Start

People keeping half an eye on my Wakoopa feed might have noticed that I started playing with RAPI Start this weekend.

RAPI Start is a command-line tool that lets you remotely run commands on a Windows Mobile device from a connected desktop. It’s quite neat, so I want to see what sort of things I can do with it.

Here is noddy first attempt number 1 🙂

The problem:
I get a text message while I’m sat at my desk with my mobile connected to my computer. (For a mobile device, it spends a large amount of it’s life tethered to a desktop – but that’s a discussion for another time!)

I want to reply, but writing on a mobile device is fiddly. I’m sat at a full-sized keyboard, so why can’t I just use that instead?

The solution:
Two quick .NET applications:

  • Windows Mobile app – taking a couple of command-line arguments (phone number, and a message) and using them to send a text message
  • Desktop app – something that uses RAPI Start to kick off the Windows Mobile app

Mobile

Let’s start with the really easy code first:

using System;
using System.Text;
using Microsoft.WindowsMobile.PocketOutlook;

namespace SmsSender
{
  class Program
  {
    static void Main(string[] args)
    {
      if (args.Length >= 2)
      {
        try
        {
          string phonenumber = args[0];
          string messageBody = args[1];

          // the first command-line argument is the phone number
          //           to send the message to
          // everything else is the message - so we concatenate 
          //   all of the arguments after the number into a single
          //   messageBody string
          for (int i = 2; i < args.Length; i++)
          {
            messageBody = messageBody + " " + args[i];
          }

          // Create and send the text message
          SmsMessage newMessage = new SmsMessage(phonenumber, messageBody);
          newMessage.Send();
        }
        catch (Exception e)
        {
          // RAPI Start doesn't return anything from the 
          //  mobile app to the desktop command-line that
          //  invoked it
          // So I don't bother doing anything clever or 
          //  useful like error-handling 
        }
      }
    }
  }
}

Well, that was easy 🙂

Desktop

This is slightly more complicated. I need to send a phone number to the mobile, so I look up a name using the desktop Outlook.

C:\> sms dale Hello, this is a text message
-> sent to 078888777888

or

C:\> sms smith Hello, I am being a bit vague with names
Identify who to send text message to:
(1) John Smith
(2) Will Smith
(3) David Smithson

This makes the desktop code a little longer, getting entries from the desktop Outlook Address Book, and looking through them for a name (or names) that matches.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Outlook;

namespace SmsDesktop
{
  class Program
  {
    static void Main(string[] args)
    {
      //----------------------------------------------
      // TWO ARGUMENTS
      //
      // the first command-line argument is the phone number
      //           to send the message to
      // everything else is the message - so we concatenate 
      //   all of the arguments after the number into a single
      //   messageBody string
      //----------------------------------------------
      if (args.Length >= 2)
      {
        string contact   = args[0];
        string messageBody = args[1];

        for (int i = 2; i < args.Length; i++)
        {
          messageBody = messageBody + " " + args[i];
        }

        // find the person to send the message to
        string numberToSendTo = contact;

        try
        {
          // is this a number? if it is, then 
          //  this function should probably work fine
          Convert.ToInt64(contact);
        }
        catch (FormatException e)
        {
          // Convert failed - throwing an exception
          // So the argument provided is not a number
          // Hopefully it is a name, instead.
          // So we look for a match in the Outlook Address Book
          ContactItem personToSendTo = SearchInOutlookAddressBook(contact);

          if (personToSendTo != null)
          {
            // if we found a matching entry in the Address Book
            //  then we grab the mobile number out of it
            numberToSendTo = personToSendTo.MobileTelephoneNumber;
          }
        }

        string cmd = "C:\\Program Files\\Windows Mobile Developer Power Toys\\RAPI_Start\\rapistart.exe";
        string arg = "bLADE_SMS " + numberToSendTo + " " + messageBody;

        ProcessStartInfo proc = new ProcessStartInfo();
        proc.WindowStyle = ProcessWindowStyle.Minimized;
        proc.FileName = cmd;
        proc.Arguments = arg;
        Process.Start(proc);
        
        Console.WriteLine(" -> sent to " + numberToSendTo);
      }
    }



    static ContactItem SearchInOutlookAddressBook(string contactDescription)
    {
      // who are we looking for? 
      //  convert the name we are searching for to lower-case (to let us
      //   do case-insensitive searching) 
      //  and break it apart into separate names to search for individually
      string[] searchNames = contactDescription.ToLowerInvariant().Split();

      // get handle to Outlook 
      Application myOutlookHandle = new Application();
      NameSpace myOutlookNS = myOutlookHandle.GetNamespace("MAPI");
      MAPIFolder outlookContacts = myOutlookNS.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
      Items contactEntries = outlookContacts.Items;

      // prepare a space to store address-book items that match
      List possibleMatches = new List();

      // look through every entry in the address-book
      for (int i = 1; i < = contactEntries.Count; i++)
      {
        if (contactEntries.Item(i) is ContactItem)
        {
          // get the next person from the Address Book
          ContactItem nxtContact = (ContactItem)(contactEntries.Item(i));

          // searching in first and last name
          //  could expand this to look in other fields like NickName
          string contactName = nxtContact.LastNameAndFirstName;

          if (contactName != null)
          {
            // convert to lowercase as we did with the searchstring
            //  as we want to do case-insensitive searching
            contactName = contactName.ToLowerInvariant();

            // look for each bit of the name we are searching for 
            bool match = true;
            foreach (string searchName in searchNames)
            {
              if (contactName.Contains(searchName) == false)
              {
                match = false;
                break;
              }
            }

            // if we found a match, we add it to the 
            //  list of possible possibleMatches
            if (match)
            {
              possibleMatches.Add(nxtContact);
            }
          }
        }
      }

      // prepare a space to store the person identified as a match
      ContactItem identifiedMatch = null;

      switch (possibleMatches.Count)
      {
        case 0:
          Console.WriteLine("No matches found.");
          break;
        case 1:
          identifiedMatch = possibleMatches[0];
          break;
        default:
          // found more than one person with a name that matches
          // 
          // so we display them in a list and get the user to 
          //  choose one

          Console.WriteLine("Identify who to send text message to:");
          for (int i = 0; i < possibleMatches.Count; i++)
          {
            ContactItem nxtMatch = possibleMatches[i];
            Console.WriteLine("(" + (i + 1) + ")  " + nxtMatch.FirstName + " " + nxtMatch.LastName);
          }
          string selected = "";
          int selIdx = 0;

          while (selIdx == 0)
          {
            selected = Console.ReadLine();

            try
            {
              selIdx = Convert.ToInt16(selected);
            }
            catch (FormatException ee)
            {
              Console.WriteLine("Enter a number.");
            }


            if ((selIdx > possibleMatches.Count) && (selIdx < = 0))
            {
              Console.WriteLine("Invalid choice. Enter again.");
              selIdx = 0;
            }
          }

          identifiedMatch = possibleMatches[selIdx - 1];
          break;
      }

      return identifiedMatch;
    }
  }
}

Okay… so ‘solution’ might be overstating things…

There are probably better ways to do this. Jeyo, for example, have a couple of neat apps that provide a desktop interface to SMS in Windows Mobile.

Or something like ActiveSync Remote Display which lets you display Windows Mobile applications on the desktop – letting me use the Windows Mobile Text Messages app directly.

But I like using the command-line – it’s certainly a very quick way to send an SMS. And it was a fun thing to tinker with while watching Doctor Who 🙂

7 Responses to “Running Windows Mobile apps from a PC using RAPI Start”

  1. dale says:

    All good code-hacking ideas should be measured in the number of Doctor Who episodes needed to finish them

    Desktop SMS : 1 x Doctor Who

    🙂

  2. […] mentioned last week that watching Doctor Who made for a good chance to absentmindedly play with some code. I seem to […]

  3. Ron says:

    Dale, I want to use the smsmessage send from your code in my windows form. I’m new at .net and try something simple as a form with textbox for number and textbox for message and event button to send message. I’m recieving exception error below. Is it possible to send message from a windows form? If so, do you have any way to fix this? Thanks,

    System.DllNotFoundException was unhandled
    Message=”Unable to load DLL ‘cemapi.dll’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)”
    Source=”Microsoft.WindowsMobile.PocketOutlook”
    TypeName=””
    StackTrace:
    at Microsoft.WindowsMobile.PocketOutlook.MAPIUtilities.SafeNativeMethods.SendSMSMessage(String RecipientAddress, String MessageText, Boolean DeliveryReportRequested)
    at Microsoft.WindowsMobile.PocketOutlook.SmsMessage.Send()
    at SmsSender.Form1.button1_Click(Object sender, EventArgs e) in C:\Users\Ron\Documents\Visual Studio 2008\Projects\SmsSender\SmsSender\Form1.cs:line 44
    at System.Windows.Forms.Control.OnClick(EventArgs e)
    at System.Windows.Forms.Button.OnClick(EventArgs e)
    at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
    at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
    at System.Windows.Forms.Control.WndProc(Message& m)
    at System.Windows.Forms.ButtonBase.WndProc(Message& m)
    at System.Windows.Forms.Button.WndProc(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
    at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
    at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
    at System.Windows.Forms.Application.Run(Form mainForm)
    at SmsSender.Program.Main() in C:\Users\Ron\Documents\Visual Studio 2008\Projects\SmsSender\SmsSender\Program.cs:line 18
    at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
    at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Threading.ThreadHelper.ThreadStart()
    InnerException:

  4. dale says:

    The error you are getting is because your code couldn’t find cemapi.dll on your desktop. This is a Windows Mobile only DLL – designed to use the Windows Mobile interfaces to mobile phone hardware. It doesn’t make any sense to try and use it on a desktop – without a mobile phone radio, you can’t send an SMS directly from a PC. Your code will need to access some third-party (e.g. a connected mobile or a web service API) to send the SMS for you.

  5. Ravi says:

    I created a bat file which uses rapistart
    rapistart “\Storage Card\test.exe””

    when I execute this in PC cmd prompt I am getting a error…
    Failed to start process (2)

    what wrong I am doing…any place where I get some example code to use rapistart

  6. Nam Cao says:

    I met a problem like Ravi, that is “Failed to start process (2)”

  7. Dercio says:

    I found the same problem when i’m runing my applications. Do someone know where can we find cemapi.dll?