Autorun in managed code (CIO summit application)
The European CIO Summit is taking place this week in Barcelona. Conference delegates will receive a T-Mobile SIM unlocked MDA device (that’s an HTC Universal to you and me). T-Mobile needed an application that can be used by the delegates to automatically configure their device for the specific operator and country settings of their own SIM and as no one else was willing and able to step up to the mark the job fell to me. The MDA device is running Windows Mobile 5.0 which means Compact Framework (CF) 1.0 SP3 is the ROM version available so the solution would need to target that version in order to avoid the install of CF 2.0.
The simplest way to apply the configuration is to use an SD card containing an Autorun.EXE application and the relevant configuration info. When any removable media is introduced to Windows Mobile, the media is checked for a processor specific directory – which happens to be 2577 for the Strong ARM processor family – and if an application named Autorun.exe is found it is copied to the device store and then run with a parameter of ‘install’. If the media is subsequently removed, Windows Mobile runs the autorun.exe application with a parameter of ‘uninstall’ before deleting the application. This autorun application can be written in native or managed code.
In prior versions of Windows Mobile this process suffer from one minor drawback: the autorun.exe is copied to the \windows directory which means if more than one media card slot is available (say SD and CF cards) an interesting race condition occurs with the autorun.exe application – the first card gets the right autorun copied and run, but the second card autorun fails to copy and the first card autorun is executed a second time. Windows Mobile 5.0 fixes this problem by copying the autorun.exe to a card specific directory.
After using CF 2.0 for some time not, stepping back to CF 1.0 was more of a challenge than I was hoping: simple things like rotation support and scrollable forms that I have come to take for granted are not available in CF 1.0 and require extra code. However I was still able to use Visual Studio 2005 and improvements such as refactoring and the improved debugger so life wasn’t so bad.
The application design is pretty straightforward:
1> Check for the ‘install’ application parameter
2> Find the media card the application came from and locate the configuration XML file.
3> Read the config file and populate a list of countries and operators.
4> When the user has selected the appropriate operator, build and apply config settings using the Microsoft.WindowsMobile.Configuration. ConfigurationManager.ProcessConfiguration class.
The application main looks like this:
static void Main(string[] args)
{
if (args.Length > 0 && args[0] == "install")
{
// Current directory
Application.Run(new ConfigForm());
}
}
On Windows Mobile 2003 devices with two or more removable media cards present there is no way of knowing which card autorun.exe came from. On Windows Mobile 5.0 the path to the executing autorun.exe includes the name of the storage card, so it’s possible to find out where the card is. I use this to locate the configuration XML file using this code:
// Get the code directory of this application
string FullPath = System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase;
// Split the path and find the storage card name used
string [] Tokens = FullPath.Split('\\');
//Pull the last but one (last one is autorun.exe)
if (Tokens.Length < 2)
{
// Not run from the right directory!
// error and exit
MessageBox.Show("This application can only be started by the system");
this.Close();
return;
}
// Now build the file name of the config file:
StorageCardName = Tokens[Tokens.Length - 2];
string ConfigFile = string.Format("\\{0}\\ConfigCarriers.xml", StorageCardName);
As the MDA device doesn’t just support rotation, it actively encourages it, so it’s important that the application looks good in both orientations. CF 1.0 doesn’t support docking and anchoring so I need to add code to deal with rotation. I do this through the Form.Resize event. This example is from the main form and you can see its pretty straight forward:
private void ConfigForm_Resize(object sender, EventArgs e)
{
// Move all the controls ect.
// check the orientation ans resize appropriately
this.BrandingBanner.Location = new Point(0, 0);
this.BrandingBanner.Width = this.Width;
this.ConfigList.Width = this.Width-8;
this.DescriptionLabel.Width = this.Width;
// Check for orientation
if (this.Width > this.Height)
{
// Landscape
this.ConfigList.Height = 65;
this.DescriptionLabel.Location = new Point(0, ConfigList.Location.Y + ConfigList.Height + 2);
this.DescriptionLabel.Height = 29;
}
else
{
// Portrait
this.ConfigList.Height = 114;
this.DescriptionLabel.Location = new Point(0, 215);
this.DescriptionLabel.Height = 54;
}
}
The configuration file is a custom XML file holding a list of operators within each country. For each operator there is a GPRS and MMS entry (both are optional) that lists the various bits of information needed to create the connections. Here is an example of the XML:
<config>
<country name="UK">
<carrier name="Vodafone">
<GPRS APN="wap.vodafone.co.uk" username="user@vodafone.net" password="user"/>
<MMS APN="wap.vodafone.co.uk" IP="212.183.137.12" server="http://mms.vodafone.co.uk/servlets/mms/" port="" username="user@vodafone.net" password="user"/>
</carrier>
<carrier name="Orange">
<GPRS APN="orangeinternet" username="Orange" password="Multimedia"/>
<MMS APN="orangemms" IP="192.168.224.10" port="" server="http://mms.orange.co.uk/" username="Orange" password="Multimedia"/>
</carrier>
</config>
Another area CF 1.0 is lacking when compared to CF 2.0 is XML processing. CF 2.0 supports XPath searching of XML data, but for CF 1.0 we need to write code to navigate the structure manually. In this project I created a “country” class that holds a list of each carrier, and a “carrier” class that holds all the settings for that carrier within a specific country. This is the sort of code used to navigate the XML file:
In the form class the document is loaded and each country created using this code:
// find all the countries and create the list
XmlNode WorkCountryNode = xmlDom.DocumentElement.FirstChild;
if (WorkCountryNode != null && WorkCountryNode.Name == "country")
do
{
// Load the country
Country workCountryClass = new Country(WorkCountryNode);
CountryCombo.Items.Add(workCountryClass);
} while (null != (WorkCountryNode=WorkCountryNode.NextSibling));
The constructor for the Country looks for each of the carriers using the carrier class constructor and the code looks like this:
public Country(XmlNode node)
{
// Get the name
displayName = node.Attributes["name"].InnerText;
carriers = new ArrayList();
// find all the operators
XmlNode WorkCarrierNode = node.FirstChild;
if (WorkCarrierNode != null && WorkCarrierNode.Name == "carrier")
do
{
// Load the country
CarrierSettings workCarrierClass = new CarrierSettings(WorkCarrierNode);
carriers.Add(workCarrierClass);
} while (null != (WorkCarrierNode = WorkCarrierNode.NextSibling));
}
The Carrier class constructor then loads the relevant settings from the XML:
public CarrierSettings(XmlNode node)
{
// Find the GPRS and MMS child nodes
carrierName = node.Attributes["name"].InnerText;
// find all the settings
XmlNode WorkNode = node.FirstChild;
if (WorkNode != null)
do
{
switch (WorkNode.Name)
{
case "GPRS":
// Read the attributes
hasGPRS = true;
_GPRSApn = WorkNode.Attributes["APN"].InnerText;
_GPRSUser = WorkNode.Attributes["username"].InnerText;
_GPRSPassword = WorkNode.Attributes["password"].InnerText;
break;
case "MMS":
hasMMS = true;
_MMSApn = WorkNode.Attributes["APN"].InnerText;
_MMSUser = WorkNode.Attributes["username"].InnerText;
_MMSPassword = WorkNode.Attributes["password"].InnerText;
_MMSIPAddr = WorkNode.Attributes["IP"].InnerText;
_MMSServer = WorkNode.Attributes["server"].InnerText;
_MMSPort = WorkNode.Attributes["port"].InnerText;
break;
default:
//Error
throw new Exception("blah…");
}
} while (null != (WorkNode = WorkNode.NextSibling));
}
So let’s say the user has selected the country and operator, reviewed the summary screen and hit Apply. The carrier class also has the “apply” function that is used to stamp the settings onto the device. The settings are applied using device configuration XML – this is a feature that’s been available on Pocket PC since the Windows Mobile 2003 and is based on the wap standards for device configuration. The XML templates are placed in the CarrierSettings.cs file. Here is an example:
private static string CMNetworksXML =
"<wap-provisioningdoc>" +
"<characteristic type=\"CM_Networks\">" +
"<characteristic type=\"{0}\">"+
"<parm name=\"DestId\" value=\"{{{1}}}\" />"+
"</characteristic>" +
"</characteristic>" +
"</wap-provisioningdoc>";
XML configuration is placed in the wap-provisioningdoc container and the first characteristic identifies which configuration we want to change. There are aver 20 specific configuration managers on Windows Mobile 5.0. CM_Networks in this example identifies the list of networks available on the device. For more information on the available configurations please refer to the Windows Mobile 5.0 SDK documentation.
The application has two settings to change:
1> Set the default GPRS endpoint to the selected value
2> Modify the MMS application configuration to set the default entry.
In order to set the GPRS endpoint this is what it does:
· Create a new GPRS entry for the “My ISP” network using CM_GPRSEntries service provider.
· Set the new value as default entry within “My ISP”.
The MMS client is not a Microsoft product and is an application T-Mobile are using on the MDA device so the following configuration might not work for any other device type. The MMS client doesn’t have a Configuration Service Provider (CSP) so I use the generic “Registry” CSP to set config information. The MMS client holds its state data here: “HKLM\SOFTWARE\Arcsoft\arcsoft MMS UA\Config\mm1” and consists of a number of sub keys containing MMS configuration data and a counter pointing at the default. To configure the MMS connection these are the steps:
· Read the current count of entries and the current default.
· Create a new CM_Network entry for our MMS operator and a new CM_GPRSEntries within it for the connection information provided. The new network entry will have a new GUID value against it that we will use later. (I need to step into native code to generate a new GUID in CF 1.0 – that’s fixed in CF 2.0).
· Increment the MMS counter and create a new subkey using the new GUID to identify the network needed.
One problem I found very late in the dev was that I wasn’t setting the default network to “My ISP” – by default the MDA device points at the T-Mobile network for both MMS and GPRS. Due to timelines I put this as two manual steps after install has completed, to select the default Network and set the default MMS settings.
Help yourself to the code but please remember this is an example and not production code. You can use this code as a template to assist you creating your own autorun, but it is your responsibility to test and apply all good coding practices before release.
Grab the code here.
Marcus