Setting Windows internet connection proxy from C#

I have a problem with my laptop where I connect to my home network and my work network, and my school network … and I have to use different proxy settings for Windows internet connection settings (which are used by virtually all .Net apps, including PowerShell) by default…

Of course, there are a lot of different apps that can manage those settings (and more) automatically using Windows NLA to trigger. However, I have an extra special requirement: when I plug in at work or at home, I want to run Synergy on the laptop as a client … but the name and ip of the host is different, of course — so I need to stop synergy and restart it with a different server name … something I can easily accomplish from a script or batch file … but changing proxies is a bit trickier.

I thought it would a simple call to a .Net library method, but after much searching, the only way of setting or retrieving Internet options seems to be through the old WinInet API, using InternetSetOptions (or InternetQueryOption to read them). Of course, to use these, you have to map the INTERNET_PER_CONN_OPTION_LIST and INTERNET_PER_CONN_OPTION structures to C#, and do whole lot of marshalling and manual memory management.

I did find some C# mappings for the Option structure on PInvoke.net but not for the others. I actually started writing them all by hand and then found a “recipe” in the (see the examples link) C# 3.0 Cookbook for retrieving Internet settings which had versions of all the structures I needed, as well as definitions for the flags I wanted. So… without further ado, let me share the code with you …


#require -version 2.0
## Set and unset the Windows Internet Settings Proxy
## Works instantly on already running apps like IE ...
###################################################################################################
## Usage:
## Set-Proxy web.proxy.xerox.com 8000
## Remove-Proxy
###################################################################################################
add-type $([IO.File]::ReadAllText("$PsScriptRoot\PoshHttp.Proxies.cs"))
 
function Set-Proxy( [string] $proxy, [int]$port ) {
   if($port) { $proxy += ":$port" }
   $null = [PoshHttp.Proxies]::SetProxy( $proxy, $null )
}
 
function Remove-Proxy() {
   $null = [PoshHttp.Proxies]::SetProxy($Null,$Null)
}

Export-ModuleMember Set-Proxy, Remove-Proxy

You’ll notice the first line of that calls add-type and passes it a C# file … Add-Type has the very impressive feature of being able to compile C# code straight into memory. The C# code is here below … and if you wanted to use it on PowerShell version 1, all you’d have to do is compile it using csc (which is in C:\WINDOWS\Microsoft.NET\Framework\ ... in either the v3.5 directory or the v2.0.50727 directory, your choice): csc /target:library .\PoshHttp.Proxies.cs and you could then load the dll by using [Reflection.Assembly]::LoadFrom("PoshHttp.Proxies.dll")! Amazing ;)

I’ll actually be adding this to my PoshHttp module and releasing a new build of that sometime this coming week, because I think it would be useful to have it pre-compiled … but I thought I would show off how cool it is to be able to use heavy-duty pinvoke stuff from a “script” ;)


using System;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace PoshHttp
{
   public class Proxies
   {
      public static bool UnsetProxy()
      {
         return SetProxy(null, null);
      }
      public static bool SetProxy(string strProxy)
      {
         return SetProxy(strProxy, null);
      }

      public static bool SetProxy(string strProxy, string exceptions)
      {
         InternetPerConnOptionList list = new InternetPerConnOptionList();

         int optionCount = string.IsNullOrEmpty(strProxy) ? 1 : (string.IsNullOrEmpty(exceptions) ? 2 : 3);
         InternetConnectionOption[] options = new InternetConnectionOption[optionCount];
         // USE a proxy server ...
         options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS;
         options[0].m_Value.m_Int = (int)((optionCount < 2) ? PerConnFlags.PROXY_TYPE_DIRECT : (PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY));
         // use THIS proxy server
         if (optionCount > 1)
         {
            options[1].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER;
            options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy);
            // except for these addresses ...
            if (optionCount > 2)
            {
               options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS;
               options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions);
            }
         }

         // default stuff
         list.dwSize = Marshal.SizeOf(list);
         list.szConnection = IntPtr.Zero;
         list.dwOptionCount = options.Length;
         list.dwOptionError = 0;


         int optSize = Marshal.SizeOf(typeof(InternetConnectionOption));
         // make a pointer out of all that ...
         IntPtr optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length);
         // copy the array over into that spot in memory ...
         for (int i = 0; i < options.Length; ++i)
         {
            IntPtr opt = new IntPtr(optionsPtr.ToInt32() + (i * optSize));
            Marshal.StructureToPtr(options[i], opt, false);
         }

         list.options = optionsPtr;

         // and then make a pointer out of the whole list
         IntPtr ipcoListPtr = Marshal.AllocCoTaskMem((Int32)list.dwSize);
         Marshal.StructureToPtr(list, ipcoListPtr, false);

         // and finally, call the API method!
         int returnvalue = NativeMethods.InternetSetOption(IntPtr.Zero,
            InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION,
            ipcoListPtr, list.dwSize) ? -1 : 0;
         if (returnvalue == 0)
         {  // get the error codes, they might be helpful
            returnvalue = Marshal.GetLastWin32Error();
         }
         // FREE the data ASAP
         Marshal.FreeCoTaskMem(optionsPtr);
         Marshal.FreeCoTaskMem(ipcoListPtr);
         if (returnvalue > 0)
         {  // throw the error codes, they might be helpful
            throw new Win32Exception(Marshal.GetLastWin32Error());
         }

         return (returnvalue < 0);
      }
   }

   #region WinInet structures
   [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
   public struct InternetPerConnOptionList
   {
      public int dwSize;               // size of the INTERNET_PER_CONN_OPTION_LIST struct
      public IntPtr szConnection;         // connection name to set/query options
      public int dwOptionCount;        // number of options to set/query
      public int dwOptionError;           // on error, which option failed
      //[MarshalAs(UnmanagedType.)]
      public IntPtr options;
   };

   [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
   public struct InternetConnectionOption
   {
      static readonly int Size;
      public PerConnOption m_Option;
      public InternetConnectionOptionValue m_Value;
      static InternetConnectionOption()
      {
         InternetConnectionOption.Size = Marshal.SizeOf(typeof(InternetConnectionOption));
      }

      // Nested Types
      [StructLayout(LayoutKind.Explicit)]
      public struct InternetConnectionOptionValue
      {
         // Fields
         [FieldOffset(0)]
         public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime;
         [FieldOffset(0)]
         public int m_Int;
         [FieldOffset(0)]
         public IntPtr m_StringPtr;
      }
   }
   #endregion

   #region WinInet enums
   //
   // options manifests for Internet{Query|Set}Option
   //
   public enum InternetOption : uint
   {
      INTERNET_OPTION_PER_CONNECTION_OPTION = 75
   }

   //
   // Options used in INTERNET_PER_CONN_OPTON struct
   //
   public enum PerConnOption
   {
      INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags
      INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers.  
      INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server.  
      INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script.  

   }

   //
   // PER_CONN_FLAGS
   //
   [Flags]
   public enum PerConnFlags
   {
      PROXY_TYPE_DIRECT = 0x00000001,  // direct to net
      PROXY_TYPE_PROXY = 0x00000002,  // via named proxy
      PROXY_TYPE_AUTO_PROXY_URL = 0x00000004,  // autoproxy URL
      PROXY_TYPE_AUTO_DETECT = 0x00000008   // use autoproxy detection
   }
   #endregion

   internal static class NativeMethods
   {
      [DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)]
      [return: MarshalAs(UnmanagedType.Bool)]
      public static extern bool InternetSetOption(IntPtr hInternet, InternetOption dwOption, IntPtr lpBuffer, int dwBufferLength);
   }
}

Similar Posts:

2 thoughts on “Setting Windows internet connection proxy from C#”

  1. Great stuff.
    I’ll try this out asap.
    One question: Why did you comment out
    //listHandle.Free(); ?

  2. My apologies, that line should have been deleted, not commented out. It was from a previous attempt to use the API the way it was documented elsewhere, so I’ve edited the post to remove it …

    The original code which I copied allocated the memory a different way and needed to use listHandle.Free(); instead of Marshal.FreeCoTaskMem( ipcoListPtr );

    When I switched the way I allocated memory for the native code calls, I originally left all of the original code in (commented out), but I simply missed that line when I was cleaning it up.

Comments are closed.