Our company enabled the Internet Explorer Proxy via group policy a while back to “improve performance”. The improvement is about 10 times slower than it was before. They insist that it is faster, but they are WRONG (yes Ken and Kevin…I’m talking about you). Not only is it much slower, but the “bypass proxy on local addresses” doesn’t work right, so our WCF endpoints don’t work correctly without configuring them for the proxy (which we can only use message security if we do this).
[more Read on for my solution…]
Our group policy typically has these options disabled. So it’s quite frustrating to need to turn that off for WCF. Obviously FireFox fixed the problem for web browsing, but for WCF, this was still a problem.
So, I wrote a C# program to poll the Internet Options proxy settings, and notify me when the settings are changed, display an icon on the tray to remind me, and gives me a one-click option to toggle the proxy off when needed, then back on. It’s not a terribly complicated program, but it demonstrates two interesting points:
- P/Invoke variable sized arrays
- WinForms application with NotifyIcon without having a Form at all by using a custom ApplicationContext.
So, to create a Formless Windows Forms Application, create a new Windows Forms Application, and remove the form. Then create a new class deriving from ApplicationContext. Add relevant code. My proxy code looks like this:
1 class ProxyMonitorApplicationContext : ApplicationContext
2 {
3 private Container _components;
4 private NotifyIcon _notifyIcon;
5 private ContextMenu _menu;
6 private Timer _timer;
7 private bool? _enabled;
8
9 public ProxyMonitorApplicationContext()
10 {
11 this.InitializeContext();
12 }
13
14 protected void InitializeContext()
15 {
16 _components = new Container();
17
18 _menu = new ContextMenu(new MenuItem[] {
19 new MenuItem(Resources.ToggleMenu, new EventHandler(this.Menu_ToggleClicked)),
20 new MenuItem(Resources.ExitMenu, new EventHandler(this.Menu_ExitClicked))
21 }
22 );
23
24 _notifyIcon = new NotifyIcon(_components);
25 _notifyIcon.Visible = true;
26 _notifyIcon.MouseClick += new MouseEventHandler(NotifyIcon_MouseClick);
27 _notifyIcon.BalloonTipClicked += new EventHandler(NotifyIcon_BalloonTipClicked);
28 _notifyIcon.ContextMenu = _menu;
29
30 _timer = new Timer(_components);
31 _timer.Enabled = true;
32 _timer.Interval = Settings.Default.QueryInterval;
33 _timer.Tick += new EventHandler(Timer_Tick);
34
35 UpdateStatus(false);
36 }
37
38 protected override void Dispose(bool disposing)
39 {
40 if (disposing && (this._components != null))
41 {
42 this._components.Dispose();
43 _notifyIcon.Dispose();
44 _timer.Dispose();
45 _menu.Dispose();
46 }
47 base.Dispose(disposing);
48 }
49
50 private void Timer_Tick(object sender, EventArgs e)
51 {
52 bool enabled = InternetOptionsProxy.IsProxyEnabled();
53 if (_enabled != enabled)
54 {
55 UpdateStatus(true);
56 ShowNotification();
57 }
58 _enabled = enabled;
59 }
60
61 void NotifyIcon_MouseClick(object sender, MouseEventArgs e)
62 {
63 if (e.Button == MouseButtons.Left)
64 {
65 UpdateStatus(false);
66 ShowNotification();
67 }
68 }
69
70 void NotifyIcon_BalloonTipClicked(object sender, EventArgs e)
71 {
72 ToggleStatus();
73 }
74
75 void Menu_ToggleClicked(object sender, EventArgs e)
76 {
77 ToggleStatus();
78 }
79
80 void Menu_ExitClicked(object sender, EventArgs e)
81 {
82 this.ExitThread();
83 }
84
85 private void ShowNotification()
86 {
87 _notifyIcon.BalloonTipTitle = (this._enabled.HasValue && this._enabled.Value)
88 ? Resources.EnabledTitle
89 : Resources.DisabledTitle;
90 _notifyIcon.BalloonTipText = (this._enabled.HasValue && this._enabled.Value)
91 ? Resources.EnabledMessage
92 : Resources.DisabledMessage;
93 _notifyIcon.BalloonTipIcon = (this._enabled.HasValue && this._enabled.Value)
94 ? ToolTipIcon.Warning
95 : ToolTipIcon.Info;
96 _notifyIcon.ShowBalloonTip(Settings.Default.NotificationDuration);
97 }
98
99 private void ToggleStatus()
100 {
101 InternetOptionsProxy.EnableProxy((_enabled == null || _enabled.Value == false));
102 UpdateStatus(false);
103 }
104
105 private void UpdateStatus(bool popupIfChanged)
106 {
107 bool enabled = InternetOptionsProxy.IsProxyEnabled();
108 bool changed = _enabled != enabled;
109 if (!changed) return;
110
111 _enabled = enabled;
112 if (changed && popupIfChanged)
113 {
114 ShowNotification();
115 }
116
117 _notifyIcon.Text = (this._enabled.HasValue && this._enabled.Value)
118 ? Resources.EnabledTitle
119 : Resources.DisabledTitle;
120 _notifyIcon.BalloonTipTitle = _notifyIcon.Text;
121 _notifyIcon.BalloonTipText = (this._enabled.HasValue && this._enabled.Value)
122 ? Resources.EnabledMessage
123 : Resources.DisabledMessage;
124 _notifyIcon.Icon = new Icon(
125 (this._enabled.HasValue && this._enabled.Value)
126 ? Resources.ProxyEnabled :
127 Resources.ProxyDisabled, new Size(16, 16)
128 );
129 }
130 }
Essentially, just make an InitializeComponent method, to create an initialize the components that are typically sited on a Form, and be sure to override the Dispose method.
Then create a Program.cs or whatever class you like for the static Main method. In the Application.Run method, pass a new instance of your context instead of the default form.
1 [STAThread]
2 static void Main()
3 {
4 Application.EnableVisualStyles();
5 Application.SetCompatibleTextRenderingDefault(false);
6 Application.Run(new ProxyMonitorApplicationContext());
7 }
Now, we will P/Invoke methods to actually turn on and off the Internet proxy server. So here’s the P/Invoke signatures that I used:
1 internal static class NativeMethods
2 {
3 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
4 public class INTERNET_PER_CONN_OPTION_LIST
5 {
6 public int dwSize;
7 [MarshalAs(UnmanagedType.LPWStr)]
8 public string pszConnection;
9 public int dwOptionCount;
10 public int dwOptionError;
11 public IntPtr pOptions;
12 }
13
14 [StructLayout(LayoutKind.Explicit, Size = 12)]
15 public struct INTERNET_PER_CONN_OPTION
16 {
17 // Fields
18 [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
19 [FieldOffset(0)]
20 public int dwOption;
21 [FieldOffset(4)]
22 public IntPtr ptrValue;
23 }
24
25 public const int PROXY_TYPE_AUTO_DETECT = 8;
26 public const int PROXY_TYPE_AUTO_PROXY_URL = 4;
27 public const int PROXY_TYPE_DIRECT = 1;
28 public const int PROXY_TYPE_PROXY = 2;
29 public const int INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME = 8;
30 public const int INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL = 9;
31 public const int INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS = 7;
32 public const int INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL = 6;
33 public const int INTERNET_PER_CONN_AUTOCONFIG_URL = 4;
34 public const int INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5;
35 public const int INTERNET_PER_CONN_FLAGS = 1;
36 public const int INTERNET_PER_CONN_PROXY_BYPASS = 3;
37 public const int INTERNET_PER_CONN_PROXY_SERVER = 2;
38 public const int INTERNET_OPTION_PER_CONNECTION_OPTION = 0x4b;
39 public const int INTERNET_OPTION_REFRESH = 0x25;
40
41 [DllImport("kernel32.dll")]
42 public static extern IntPtr GlobalFree(IntPtr hMem);
43 [DllImport("wininet.dll", SetLastError = true)]
44 [return: MarshalAs(UnmanagedType.Bool)]
45 public static extern bool InternetQueryOption(IntPtr hInternet, uint dwOption, INTERNET_PER_CONN_OPTION_LIST lpBuffer, ref int lpdwBufferLength);
46 [DllImport("wininet.dll")]
47 [return: MarshalAs(UnmanagedType.Bool)]
48 public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, INTERNET_PER_CONN_OPTION_LIST lpBuffer, int dwBufferLength);
49 }
And a helper class to call the methods from a nicer .NET friendly API:
1 internal static class InternetOptionsProxy
2 {
3 public static bool IsProxyEnabled()
4 {
5 NativeMethods.INTERNET_PER_CONN_OPTION_LIST list = new NativeMethods.INTERNET_PER_CONN_OPTION_LIST();
6 NativeMethods.INTERNET_PER_CONN_OPTION[] options = new NativeMethods.INTERNET_PER_CONN_OPTION[5];
7 options[0].dwOption = NativeMethods.INTERNET_PER_CONN_AUTOCONFIG_URL;
8 options[1].dwOption = NativeMethods.INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
9 options[2].dwOption = NativeMethods.INTERNET_PER_CONN_FLAGS;
10 options[3].dwOption = NativeMethods.INTERNET_PER_CONN_PROXY_BYPASS;
11 options[4].dwOption = NativeMethods.INTERNET_PER_CONN_PROXY_SERVER;
12
13 list.dwSize = Marshal.SizeOf(list);
14 list.pszConnection = null;
15 list.dwOptionCount = options.Length;
16 list.dwOptionError = 0;
17
18 int len = Marshal.SizeOf(options[0]);
19 IntPtr buffer = Marshal.AllocHGlobal((int)(len * options.Length));
20 for (int i = 0; i < options.Length; i++)
21 {
22 Marshal.StructureToPtr(options[i], new IntPtr(buffer.ToInt64() + (i * len)), false);
23 }
24 list.pOptions = buffer;
25 int lpdwBufferLength = Marshal.SizeOf(list);
26
27 NativeMethods.InternetQueryOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_PER_CONNECTION_OPTION,
28 list, ref lpdwBufferLength);
29 for (int i = 0; i < options.Length; i++)
30 {
31 options[i] = (NativeMethods.INTERNET_PER_CONN_OPTION)Marshal.PtrToStructure(
32 new IntPtr(buffer.ToInt64() + (i * len)), typeof(NativeMethods.INTERNET_PER_CONN_OPTION));
33 }
34
35 Marshal.PtrToStringAnsi(options[0].ptrValue);
36 Marshal.PtrToStringAnsi(options[3].ptrValue);
37 Marshal.PtrToStringAnsi(options[4].ptrValue);
38
39 int flags = options[2].ptrValue.ToInt32();
40 NativeMethods.GlobalFree(options[0].ptrValue);
41 NativeMethods.GlobalFree(options[3].ptrValue);
42 NativeMethods.GlobalFree(options[4].ptrValue);
43 Marshal.FreeHGlobal(buffer);
44
45 bool enabled = (((flags & NativeMethods.PROXY_TYPE_AUTO_DETECT) > 0) ||
46 (((flags & NativeMethods.PROXY_TYPE_AUTO_PROXY_URL) > 0) ||
47 ((flags & NativeMethods.PROXY_TYPE_PROXY) > 0)));
48
49 return enabled;
50 }
51
52 public static void EnableProxy(bool enable)
53 {
54 NativeMethods.INTERNET_PER_CONN_OPTION_LIST list = new NativeMethods.INTERNET_PER_CONN_OPTION_LIST();
55 NativeMethods.INTERNET_PER_CONN_OPTION[] options = new NativeMethods.INTERNET_PER_CONN_OPTION[1];
56 options[0].dwOption = NativeMethods.INTERNET_PER_CONN_FLAGS;
57 options[0].ptrValue = enable ?
58 new IntPtr(NativeMethods.PROXY_TYPE_AUTO_PROXY_URL |
59 NativeMethods.PROXY_TYPE_PROXY |
60 NativeMethods.PROXY_TYPE_DIRECT
61 )
62 : new IntPtr(NativeMethods.PROXY_TYPE_DIRECT);
63
64 list.dwSize = Marshal.SizeOf(list);
65 list.pszConnection = null;
66 list.dwOptionCount = options.Length;
67 list.dwOptionError = 0;
68
69 int len = Marshal.SizeOf(options[0]);
70 IntPtr hglobal = Marshal.AllocHGlobal((int)(len * options.Length));
71 for (int i = 0; i < options.Length; i++)
72 {
73 Marshal.StructureToPtr(options[i], new IntPtr(hglobal.ToInt64() + (i * len)), false);
74 }
75 list.pOptions = hglobal;
76
77 int dwBufferLength = Marshal.SizeOf(list);
78 NativeMethods.InternetSetOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_PER_CONNECTION_OPTION,
79 list, dwBufferLength);
80 NativeMethods.InternetSetOption(IntPtr.Zero, NativeMethods.INTERNET_OPTION_REFRESH, null, 0);
81 Marshal.FreeHGlobal(hglobal);
82 }
83 }
This class does the P/Invoke work.
When the application is running, right clicking the tray icon allows toggling from a “proxy enabled” to “proxy disabled” state.

When the proxy is enabled and traffic is going through the proxy, the icon shows red monitors.

When the proxy is disabled and traffic is not going through the proxy, the icon shows blue monitors.

Left-clicking the icon shows the status in a balloon tip. Clicking the balloon will toggle the proxy status.

Note: When enabling the proxy, it only turns on the currently configured proxy. It will not configure or autoconfigure a proxy. If there is no proxy configured, I don’t know what will happen. I haven’t tried it.
Download the full project here or run a ClickOnce application directly here.
Development
.net | c# | wpf