Introduction
I don't know about you, but one of the things that bores me the most about being a "MobilePC" user is that I have to do the same things again and again and again and again…
So, this little app is what I have done in an attempt to remove the pain of being a mobile user. Its only purpose is to react to changes in your network environment and try to set your preferences accordingly. Let's say, for the sake of the example, that you use your laptop in 3 distinct places (at home, at work, and in the bus). You certainly change your default printer about 99 times a week, probably swear about that …. proxy you have at work, etc.
Wouldn't it be cool if your laptop just adapted to your environment?
NB: this article is not about the code in itself, but about the application, and how to use it. The only "tricky" (but really easy) bit of code is about the Network Awareness API (see the corresponding paragraph).
Definitions
Some definitions about the terms that I will use in this article.
NetworkConfiguration
A network configuration is an environment in which your MobilePC can be. Examples of NetworkConfiguration
s are : « At Home », « At work », « At client X », « Disconnected ». A NetworkConfiguration
is defined by its tests – which identifies the state of network settings which identifies the environment – and its netlets –which are commands to execute in order to configure the system.
Tester
A tester
is a class that allows to determine if your MobilePC is in a NetworkConfiguration
. Examples : « Is My PC connected to DHCP server 192.168.0.1 ?» A NetworkConfiguration
is called « active » if all of it's tests pass.
Netlet
A netlet
is a command that is executed when a NetworkConfiguration
is activated or disactivated. Its purpose is to set the environment up, such as changing the default printer, mapping a network drive or moving files to the desktop/start menu.
How does it work?
A small, notification-area-based application runs in the background of your session and reacts to changes in your network environment. Using a set of rules/Testers, it selects from the configurations that you have set-up the one that is currently active, and configures your systems accordingly.
Let's say that you use your laptop in three environments: at home, at work, and on the bus (or anywhere else without a proper network connection); between those three environments, you may want to change some of your settings such as your default printer, the proxy used to connect to the Internet, etc. What you need is something like:
Home | Office | Everywhere else (when disconnected) |
Use my inkjet printer | Use a big laser printer | Print to PDF files |
Connect to the internet through my ISP router | Use my corporate proxy | |
Map Z: to \\family\share | | |
| Get shortcuts to my server or applications on my desktop | |
You just have to configure three distinct NetworkConfiguration
s, and try to define what distinguishes one from another. When you have set-up the conditions (Tester
s) that uniquely identifies one configuration, you may add one or more commands (Netlet
s) which will change the state of your system.
Home | Office | Everywhere else (when disconnected) |
When those conditions are met (Testers) |
There is a network present | There is a network present | There is no network connection |
My DHCP Server is 192.168.0.254 | My DHCP Server is 10.0.38.253 | |
| My DNS suffix is mycompany.com | |
What to do ? (Netlets) |
Change default printer to My Inkjet | Change default Printer to \\Server\Printer2 | Change default printer to PDFPrint |
Map Drive Z: to \\family\share | | |
| Copy *.rdp to my Desktop | |
| Set IE Proxy to 10.0.38.254 | |
A netlet
will change to the desired state when a configuration is activated and revert back to the previous state when needed, so you don't have to worry about removing the proxy on IE on the Home profile if it is not set by default.
The Network Awareness API
The core of this software is the Network Awareness API which is shipped with Windows XP & Vista. For managed users, this API is available thru the System.Net.NetworkInformation
namespace. The NetworkChange
static
class in this namespace contains two interesting events:
NetworkAvailabilityChanged
which is fired when your MobilePC connects (or disconnects) to any network NetworkAddressChanged
fired when any of your network adapters receives a new IP address.
Using these events, it's rather easy to make an application respond to network changes (NB: this is not the code used by the application, but a rather heavily stripped down version for easier understanding):
private class MyAssistant
{
public void Initialize()
{
NetworkChange.NetworkAddressChanged +=
new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);
}
bool IsConnected
{
get
{
return NetworkInterface.GetIsNetworkAvailable();
}
}
void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
if (IsConnected)
ChangeDefaultPrinter("MyInkjet");
else
ChangeDefaultPrinter("PDFPrinter");
}
}
I've used the NetworkAddressChanged
event because I need the IP Address to be set for some tests (on DHCP or DNS settings). This code sample introduces the other important class of this namespace NetworkInterface
which allows getting the settings from your network cards (and other devices). For this application, we need to find if a particular DHCP or DNS is used, so we'll iterate through all of our network interfaces, get their IP-related parameters and look for a known server:
void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
if (!IsConnected)
ChangeDefaultPrinter("PDFPrint");
else if(FindDhcp(IPAddress.Parse("192.168.0.254")))
ChangeDefaultPrinter("MyInkjet");
else if(FindDhcp(IPAddress.Parse("10.0.38.253")))
ChangeDefaultPrinter(@"\\Server\Printer2");
}
private bool FindDhcp(IPAddress dhcpAddress)
{
foreach (NetworkInterface card in NetworkInterface.GetAllNetworkInterfaces())
{
IPInterfaceProperties props = card.GetIPProperties();
foreach (IPAddress cardDhcp in props.DhcpServerAddresses)
{
if (cardDhcp.Equals(dhcpAddress))
return true;
}
}
return false;
}
Configuration
All the configuration of this assistant is done by through an XML-based settings file which you can edit from your start menu (Start>All Programs>Network Assistant v0.5>Edit your NetworkConfiguration
s). It's format is rather simple:
- Each
NetworkConfiguration
must be a child of the root Config
element and must have a name
attribute - A
NetworkConfiguration
should have 2 child elements: Tests
and Netlets
- In
Tests
, you can put one of the following elements:
IsConnected
DnsServer
DnsSuffix
DhcpServer
- In
Netlets
, the following children are allowed:
DefaultPrinter
ConnectNetworkDrive
CopyFiles
IEProxy
- (more
netlets
will be added later)
A test element (IsConnected
, DnsServer
…) always has the same format:
<TestName value="valueToTest" />
Test name | Value parameter | Remark |
IsConnected | "true " or "false " | If you want the test to pass when a network is available use "true ", and if you want to test if the MobilePC is in a disconnected state use "false " |
DhcpServer | IP address | You must use an IP address (192.168.0.1). Using a computer hostname is not supported |
DnsServer | IP address | (idem) |
DnsSuffix | DNS suffix | Ex : "carbenay.info" |
The Tests node for the Home configuration is:
<Tests>
<IsConnected value="true" />
<DnsServer value="192.168.0.1" />
</Tests>
The netlet
s have not the same "standard" parameter so each one is different.
NetletName | Parameter | Remarks |
DefaultPrinter | printerName="{PrinterName}" | |
CopyFiles | sourceFolder="{Path}" | The {Path} parameter can include environment variables (such as %USERNAME%, %APPDATA% …) and can also use: %DESKTOP% : path to the current user's desktop (more aliases will be added) |
| destinationFolder="{Path}" | Idem |
| searchPattern="{SearchPattern}" | i.e.: "*.rdp", "*.lnk"… |
ConnectNetworkDrive | localDrive="{DriveLetter}" | Drive letter (in the format X:) on which to mount the network share |
| remotePath="{RemotePath}" | Path to the network share in UNC format (i.e.: \\server\share) |
OpenFirewallPort | port="{PortNumber}" | port number. |
This netlet only works on Windows XP. Vista would require Privilege elevation | protocol="{Protocol}" | Protocol can be either "TCP " or "UDP " |
For example: the Home configuration's Netlet
s
element is shown below:
<Netlets>
<DefaultPrinter printerName="MyInkjet" />
<CopyFiles
sourceFolder="%LOCALAPPDATA%\NetworkAwareAssistant\Home\CopyFiles\"
destinationFolder="%DESKTOP%"
searchPattern="*.rdp"
/>
<ConnectNetworkDrive
localDrive="J:"
remotePath="\\FamilyServer\Share" />
</Netlets>
A future version of NetworkAssistant
will include a Control Panel application, but for this first release, you'll have to edit your settings manually.
Extensibility
Extending this app is rather simple: you just have to create classes that implement INetlet
, put your assembly in the application folder (or in the GAC), and add a reference to it in the NetworkAwareAssistant.xml file.
Implementing a netlet
requires you to implement an interface with 5 methods (sample code comes from the ChangeDefaultPrinterNetlet
class):
void Initialize(string configurationName, XmlElement config)
: called when the configuration file is parsed. The configurationName
parameters will be set to the name of the parent NetworkConfiguration
, and the config
parameter will be the XmlNode
of your Netlet
in the file.
public void Initialize(string stateName, System.Xml.XmlElement config)
{
if (config == null)
throw new ArgumentNullException("config");
_defaultToSet = config.GetAttribute("printerName");
}
void Connect()
: called when the NetworkConfiguration
is activated. There you should perform whatever your Netlet
is supposed to do.
public void Connect()
{
if (string.IsNullOrEmpty(_defaultToSet))
return;
using (ManagementObjectSearcher sr = new ManagementObjectSearcher(
"select * from Win32_Printer where default=1 or name=\""
+ _defaultToSet + "\""))
{
ManagementObject oldPrinter;
ManagementObject newPrinter;
string currentDefaultName;
FindPrinters(sr,
_defaultToSet,
out oldPrinter,
out newPrinter,
out currentDefaultName);
if (oldPrinter != newPrinter)
{
try
{
newPrinter.InvokeMethod("SetDefaultPrinter", new object[0]);
_defaultToRestore = currentDefaultName;
}
catch (Exception e)
{
throw new Exception("Unable to set the default printer to " +
_defaultToSet);
}
}
}
}
void Disconnect()
: called when a NetworkConfiguration
is deactivated. This method must revert the environment in its previous state if possible. In ChangeDefaultPrinterNetlet
, this method is quite similar to Connect
.
void SaveState(System.Xml.XmlElement elmToSaveTo)
: provides a way for the system to remember the current state of the NetworkConfiguration
. This method (with Load
) allows for the application to shutdown and restart. In this method you should put any data that you may need to restore the netlet
to its current state.
public void SaveState(System.Xml.XmlElement elmToSaveTo)
{
elmToSaveTo.SetAttribute("oldDefault", _defaultToRestore);
elmToSaveTo.SetAttribute("newDefault", _defaultToSet);
}
void LoadState(System.Xml.XmlElement elmToLoadFrom)
: Loads the netlet
state from a previously saved state. These two methods are only meant to do some basic serialization: they should not (except if it really makes sense for your netlet
) make any changes to the system.
public void LoadState(System.Xml.XmlElement elmToLoadFrom)
{
_defaultToRestore = elmToLoadFrom.GetAttribute("oldDefault");
_defaultToSet = elmToLoadFrom.GetAttribute("newDefault");
}
When your class is ready, there are two more things to do:
- Add a
NetletAttribute
to the class: the name parameter of this attribute will be used to identity your netlet
's configuration nodes in the NetworkAwareAssistant.xml file.
[Netlet("TraceConfigChange")]
public class TraceChangeNetlet : INetlet
{
...
}
- Add a reference to your assembly and class in NetworkAwareAssistant.xml by adding a new /Config/Assemblies/Add node and add your
netlet
to a NetworkConfiguration
.
<Config>
<Assemblies>
<Add name="MyExtensionAssembly"/>
</Assemblies>
...
<Netlets>
<TraceConfigChange />
<Netlets>
...
</Config>
Here is the sample code for a Netlet
that just writes a trace on Connect
/Disconnect
:
[Netlet("TraceConfigChange")]
public class TraceChangeNetlet : INetlet
{
private string _currentConfig = null;
public void Initialize(string stateName, System.Xml.XmlElement config)
{
_currentConfig = stateName;
}
public void Connect()
{
Trace.TraceInformation("NetworkConfiguration " +
_currentConfig +" activated");
}
public void Disconnect()
{
Trace.TraceInformation("NetworkConfiguration " +
_currentConfig +" de-activated");
}
public void LoadState(System.Xml.XmlElement elmToLoadFrom)
{
}
public void SaveState(System.Xml.XmlElement elmToSaveTo)
{
}
}
History
- 21 Apr 2007: First release
- 25 Apr 2007: Added a command to open port in firewall for Windows XP (Vista firewall would need a privilege elevation). Thanks for suggesting this goes to sides_dale.