Thursday, August 07, 2008

Creating a Network and a Connection Programmatically on Windows Mobile - Part 1

Many folks within the community ask questions about forcing a specific connection or using a proxy server in order to connect to the internet etc. Note this is only one way of achieving connectivity and in my opinion probably the best way.

I'd like to write an article on how to create a network and add a connection to a network using the CSP's Configuration Server Providers available to us under Windows CE.

First of all we use the CM_Networks CSP to create our custom network that we will use when connecting to our network. Which looks like this:



So it is fairly simple as compared to other CSPs. I have written a couple of articles about CSPs in the past here.

What is a network in the Windows Mobile space? a network is a bucket used to group certain connections, such as VPN, GPRS, 3G, proxy, GSM etc It is what your application should use when connecting to your remote network typically in LOB - Line of Business apps you'd use some form of APN - Access Point Name. You can view a list of connections on your device by navigating to Settings\Connections\Connections.


Connections screen

Navigating to the Advanced tab allows you to view or select different networks.


Advanced connections screen

Tapping select networks allows you to add, edit, delete or change connections.


Network management

Our objective is to add our own network and a private APN to this network. We can then select this network when connecting to our APN using the Connection Manager built into phone devices.

The first thing we need is the Microsoft.WindowsMobile.dll and the Microsoft.WindowsMobile.Configuration.dll both found from the WM5 or WM6 SDKs. Note: if you are targeting pre WM5 then you will need to use P/Invoke - I will talk about this later.

To create out network so that we can add connections, we need to call the CM_Networks CSP as mentioned earlier. The interface for CSPs is XML.

In the following code example we create a network named Acme Corp. I decided to use the XmlTextWriter class here but could have done this just by loading the XML into DOM using the XmlDocument class. The XML looks like the following:
<?xml version="1.0" encoding="utf-16"?>
<wap-provisioningdoc>
<characteristic type="CM_Networks">
<characteristic type="Acme Corp">
<parm name="DestId" value="{8b06c75c-d628-4b58-8fcd-43af276755fc}" />
</characteristic>
</characteristic>
</wap-provisioningdoc>
The code to produce this XML and to provision this XML is as follows:
StringBuilder sb = null;
StringWriter sw = null;
XmlTextWriter xmlWriter = null;
try
{
sb = new StringBuilder();
sw = new StringWriter(sb);
xmlWriter = new XmlTextWriter(sw);
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("wap-provisioningdoc");
xmlWriter.WriteStartElement("characteristic");
xmlWriter.WriteStartAttribute("type");
xmlWriter.WriteString("CM_Networks");
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartElement("characteristic");
xmlWriter.WriteStartAttribute("type");
xmlWriter.WriteString("Acme Corp");
xmlWriter.WriteEndAttribute(); //type
xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("DestId");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("{8b06c75c-d628-4b58-8fcd-43af276755fc}");
xmlWriter.WriteEndAttribute();
xmlWriter.WriteEndElement(); //parm
xmlWriter.WriteEndElement(); //characteristic
xmlWriter.WriteEndElement(); //characteristic
xmlWriter.WriteEndElement(); //wap-provisioningdoc
xmlWriter.WriteEndDocument();
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(sb.ToString());
ConfigurationManager.ProcessConfiguration(xmlDoc, false);
}
catch (Exception)
{
//Do stuff...
throw;
}
finally
{
if (xmlWriter != null)
{
xmlWriter.Flush();
xmlWriter.Close();
}
}

Now if we take a look in Connections\Advanced\Select Networks screen, we will see the newly created network. Although at the moment we have just a blank network with no connections defined.


Our newly created network

Tapping edit will reveal no connections as yet...


Empty network

Our next job is to add a connection to make this network useful. We do this using the CM_GPRSEntries CSP. Documentation on this CSP can be found here. 3G UMTS/HSDPA will be used if available although the CSP is named GPRS this is a little mis-leading.

Again we use the trusty old XmlTextWriter to create our XML file in order to provision to our device. The XML looks like the following:
<?xml version="1.0" encoding="utf-16"?>
<wap-provisioningdoc>
<characteristic type="CM_GPRSEntries">
<characteristic type="Acme Corp">
<parm name="DestId" value="{8b06c75c-d628-4b58-8fcd-43af276755fc}" />
<parm name="UserName" value="un" />
<parm name="Password" value="pa" />
<parm name="DnsAddr" value="192.168.0.1" />
<parm name="Domain" value="ACME" />
<parm name="AltDnsAddr" value="192.168.0.2" />
<parm name="WinsAddr" value="" />
<parm name="AltWinsAddr" value="" />
<parm name="SpecificNameServers" value="0" />
<parm name="IpHeaderCompression" value="false" />
<parm name="RequirePw" value="1" />
<parm name="RequireMsEncryptedPw" value="false" />
<parm name="RequireDataEncryption" value="false" />
<characteristic type="DevSpecificCellular">
<parm name="BearerInfoValid" value="1" />
<parm name="GPRSInfoValid" value="1" />
<parm name="GPRSInfoProtocolType" value="2" />
<parm name="GPRSInfoL2ProtocolType" value="PPP" />
<parm name="GPRSInfoAccessPointName" value="acmecorp.com" />
<parm name="GPRSInfoAddress" value="" />
<parm name="GPRSInfoDataCompression" value="2" />
<parm name="GPRSInfoHeaderCompression" value="2" />
</characteristic>
</characteristic>
</characteristic>
</wap-provisioningdoc>
The code to create this XML and provision the device looks like the following:
StringBuilder sb = null;
StringWriter sw = null;
XmlTextWriter xmlWriter = null;
try
{
sb = new StringBuilder();
sw = new StringWriter(sb);
xmlWriter = new XmlTextWriter(sw);
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("wap-provisioningdoc");
xmlWriter.WriteStartElement("characteristic");
xmlWriter.WriteStartAttribute("type");
xmlWriter.WriteString("CM_GPRSEntries");
xmlWriter.WriteEndAttribute(); //type
xmlWriter.WriteStartElement("characteristic");
xmlWriter.WriteStartAttribute("type");
//The name of the APN.
xmlWriter.WriteString("Acme Corp");
xmlWriter.WriteEndAttribute(); //type
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("DestId");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("{8b06c75c-d628-4b58-8fcd-43af276755fc}");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("UserName");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//Username to authenticate.
xmlWriter.WriteString("un");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("Password");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//Password to authenticate.
xmlWriter.WriteString("pa");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("DnsAddr");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//DNS of the APN.
xmlWriter.WriteString("192.168.0.1");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("Domain");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//Domain to use during authentication.
xmlWriter.WriteString("ACME");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("AltDnsAddr");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//An alternate DNS.
xmlWriter.WriteString("192.168.0.2");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("WinsAddr");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//The WINS server is ignored unless SpecificNameServers is set to true.
xmlWriter.WriteString("");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("AltWinsAddr");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("SpecificNameServers");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//1 = true, 0 = false
xmlWriter.WriteString("0");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("IpHeaderCompression");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("false");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("RequirePw");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("1"); //true, 0 = false
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  
xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("RequireMsEncryptedPw");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("false");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("RequireDataEncryption");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("false");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("characteristic");
xmlWriter.WriteStartAttribute("type");
xmlWriter.WriteString("DevSpecificCellular");
xmlWriter.WriteEndAttribute(); //type
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("BearerInfoValid");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("1");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoValid");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("1"); //true
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoProtocolType");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("2"); //CELLDEVCONFIG_GPRSPROTOCOL_IP
//See: http://msdn2.microsoft.com/en-us/library/ms835762.aspx
//for the CELLGPRSCONNECTIONINFO structure.
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoL2ProtocolType");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("PPP");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoAccessPointName");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("acmecorp.com");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoAddress");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("");
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoDataCompression");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
//Data compression.
xmlWriter.WriteString("2"); //true, 1 = false
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm

xmlWriter.WriteStartElement("parm");
xmlWriter.WriteStartAttribute("name");
xmlWriter.WriteString("GPRSInfoHeaderCompression");
xmlWriter.WriteEndAttribute(); //name
xmlWriter.WriteStartAttribute("value");
xmlWriter.WriteString("2"); //true, 1 = false
xmlWriter.WriteEndAttribute(); //value
xmlWriter.WriteEndElement(); //parm
  
xmlWriter.WriteEndElement(); //characteristic
xmlWriter.WriteEndElement(); //characteristic
xmlWriter.WriteEndElement(); //characteristic
xmlWriter.WriteEndElement(); //wap-provisioningdoc
  
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(sb.ToString());
ConfigurationManager.ProcessConfiguration(xmlDoc, false);
}
catch (Exception)
{
throw;
}
finally
{
if (xmlWriter != null)
{
xmlWriter.Flush();
xmlWriter.Close();
}
}
After we run this code, if we go back to the network, we will see a new connection added to this network:


Our new connection added to our custom network.

A few points to make with the above code. The DestId must be the same Id provided when creating the network. The CM_GPRSEntries is a little more complex than the network CSP.

UserName - if blank and the RequirePw is set, then the Connection Manager will ask for credentials when making a connection, otherwise authentication will fail unless anonymous access has been defined.

DnsAddr - only needs to be set if required by the APN. Set it to 0.0.0.0 to null the address. This is only ever used if SpecificNameServers is set to true (1 = true 0 = false). This is true for WinsAddr, AltDnsAddr.

IpHeaderCompression - should be set for PPP connections as it significantly improves performance. The property should be set to false only when connecting to a server that does not correctly negotiate IP header compression.

RequirePw - as mentioned before, the system should prompt for a user/password/domain name before dialing the phone. If set to true, the system will prompt if no password or user name is provisioned. If set to false, the system will not prompt for password before dialing even if no password or user name is provisioned.

RequireMsEncryptedPw - USE WITH CAUTION: determines whether MS encrypted password is required or not. If set, only the Microsoft secure password scheme, MSCHAP, can be used to authenticate the client with the server. This property prevents the PPP driver from using the PPP plain-text authentication protocol (PAP).

RequireDateEncryption - USE WITH CAUTION: determines whether data encryption is required or not. Note: MsEncryptedPassword property needs to be set in order for this property to have any effect. Also, MsEncryptedPassword is only supported by the Microsoft secure password scheme, MSCHAP (based on MD5), can be used to authenticate the client with the server. The PPP device driver disables plain PAP authentication. For interopability, set this flag to false.

GPRSInfoL2ProtocolType - PPP is the only supported protocol on Win CE devices.

GPRSInfoDataCompression - determines whether data compression for messages that are sent and received is enabled or not.

GPRSInfoHeaderCompression - header compression at message level is enabled or not. (2 = true 1 = false).

GPRSInfoAccessPointName - this is the name/IP of the APN.

Notice I have used the ConfigurationManager.ProcessConfiguration method to provision the XML which is only available on CF 2.0 and later frameworks. You have to platform invoke DMProcessConfigXML on pre CF 2.0.

In the next part, I will show how to add a proxy server to this connection.

UPDATE: As I mentioned above, I was going to show in the next post how to add a proxy connection. I've done one better, I've created a managed Configuration Service Provider API. I've written an article that has recently been published on MSDN which you can view here: http://msdn.microsoft.com/en-us/library/dd296760.aspx .

The API source code with an example can be downloaded from the MSDN Code Gallery here: http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=CSPWM&DownloadId=3943.

I hope you find it useful. In a future post, I'll build on this managed CSP with a proxy managed CSP!

4 comments:

Anonymous said...

I want to be able to set a connection's 'Connect To' option (i.e. Set it to 'Work' instead of 'The Internet') Can I use a method similar to the process you have laid out to accomplish that?

sms said...

I see Anonymous' comment everywhere and no place do I see an answer. Here it is ignored and on the MSDN web site it is the same (see my link below). I need to not only set the APN on "My Work Network" or whatever one we create with the Provisioning XML - I need to set that one as the default for both for both Internet access and enterprise access.
See it get ignored here too: http://social.msdn.microsoft.com/Forums/en-US/vssmartdevicesvbcs/thread/00b44778-f3d8-4892-a8ae-9f47401e7dc0/

Graham Downs said...

Hi,

I created a network using characteristic type="CM_Networks", and a connection for it using characteristic type="CM_GPRSEntries". It works beautifully! But now what I'm trying to do is to make that network the default network for "Programs that automatically access the internet." I've been trying the CM_Planner characteristic_type="PreferredConnections", but it's not working. Can you help?

Whosywhatsit said...

Setting all connections to My Work Network!!!

This is has been a problem with the Windows Mobile operating system for at least six years. If I ever meet the guy who designed (or is in charge of) the Win Mobile connection manager, I swear I'm going knock him out.

Currently, we're using the following in an XML config file and making it into a cpf file (I had to change the brackets into parens):

(wap-provisioningdoc)
(characteristic type="CM_ProxyEntries")
(!-- Remove default proxy entry linking "My ISP" to "The Internet" --)
(nocharacteristic type="HTTP-{ADB0B001-10B5-3F39-27C6-9742E785FCD4}" /)
(!-- Create null proxy (i.e. direct link) between "My Work Network" and "The Internet" --)
(characteristic type="HTTP-{18AD9FBD-F716-ACB6-FD8A-1965DB95B814}")
(parm name="Type" value="0"/)
(parm name="SrcId" value="{18AD9FBD-F716-ACB6-FD8A-1965DB95B814}"/)
(parm name="DestId" value="{436EF144-B4FB-4863-A041-8F905A62C572}"/)
(parm name="Proxy" value="new-inet:1159"/)
(parm name="Enable" value="1"/)
(/characteristic)
(/characteristic)
(/wap-provisioningdoc)


Creating the cpf file from xml:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/mobilesdk5/html/wce51howcreatingcpffile.asp

The problem that we run into is that IE and other apps sometimes will not connect to anything. We get the "Cannot Connect with current connection settings." alert box. If this happens, we usually have to hard-reset the device to get IE to work again.