Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PowerShell

VPNScripter: A Scripter for Windows' VPN Connections

4.84/5 (16 votes)
21 Nov 2023MIT8 min read 62.5K   1.5K  
Create quickly VPN connections in Windows according to a XML configuration file

Introduction

This is a general PowerShell script which creates automatically a set of VPN connections according to a XML configuration file. Very useful if you want to quickly configure VPN directly in Windows without using any custom VPN software developed by your VPN provider.

Background

VPNs have always been historically a way to connect to companies' private network from Internet. Nowadays, they have gone commercial being used mainly for privacy issues, routing all customer's internet traffic to relay servers distributed across the world. There are a few different protocols for VPN connections and Windows supports natively most of them, in particular:

  • PPTP: One of the first VPN protocols, based on the encapsulation (through GRE protocol) and encryption (RC4) of the PPP protocol [1]. PPTP was developed by Microsoft and it is now being abandoned due to the weak and insecure encryption algorithm. Client authentication is performed usually through a login and password, even though client certificates can be used. From a network viewpoint, PPTP uses TCP port 1723 and requires routers to forward IP protocol GRE.
  • L2TP/IPSEC: L2TP is a tunnelling protocol which like PPTP is used to encapsulate PPP protocol. Since L2TP is insecure (it does not have any kind of encryption), in order to provide security, it is often used together with another protocol, IPSEC [2]. L2TP client authentication requires a login and password and moreover client and server share usually also a PSK in order to allow the client to establish the IPSEC tunnel, even though the client can also authenticate to IPSEC server by certificates. From the network viewpoint, L2TP uses port UDP 1701, while IPSEC uses UDP ports 4500 and 500 (if not behind a NAT ip protocol ESP must also be forwarded by routers). It is the "bulkiest" protocol.
  • SSTP: Developed by Microsoft, like PPTP and L2TP, it is based on the encapsulation of PPP protocol using instead a SSL/TLS channel to secure all traffic (the same protocols used while browsing with https on secure internet websites). Like PPTP, client authentication is performed usually through a login and password, even though client certificates can be used. Since it uses only the standard HTTPS port (TCP 443), it is the most suitable protocol to bypass almost any firewall restriction and can also be used flawlessly with proxies.
  • IKEv2: It is a slight variation of IPSEC in which the client authentication is not done only by a PSK or a certificate, but also by using standard user-password credentials. Moreover, IKEv2 is much more resilient to changing network connectivity, making it a good choice for mobile users who move between access points. From a network viewpoint, IKEv2 uses UDP ports 500 and 4500 (if not behind a NAT ip protocol ESP must also be forwarded by routers).

Windows has also the automatic protocol which essentially attempts to establish the vpn connection by trying all protocols IKEv2, SSTP, L2TP/IPSEC, PPTP in order. For more information, please see [3].

Using the Code

All scripter configuration takes place in the XML file called vpnconfig.xml (see below). This file defines a set of VPN providers (Provider element node) and for each provider specifies a set of servers (Server sub element node) for each of which a VPN connection will be created in Windows. Let's examine the nodes:

  • Provider: This node corresponds to a VPN provider. Attributes:
    • name: Used to generate the VPN connection name in Windows
    • basedomain: Used to generate host name of the VPN server (usually a VPN provider offers a set of servers which share a common domain name). It can be overridden in server node.
    • l2tppsk: It's the pre-shared secret for L2TP/IPSEC connection (usually it is the same across all provider's servers).
    • user: It's the username to access the VPN (usually, it is the same across all provider's servers).
    • password: It's the password associated with the username (usually, it is the same across all provider's servers).
    • proto: Specifies the VPN protocol to use for all servers, if left empty, it means automatic.
  • Server: This node corresponds to a server of a VPN provider. Attributes:
    • server: Specifies the host name of the server. If this attribute does not contain the "." character, the host name is obtained by concatenating this field to provider's base domain, otherwise host name is equal to this field.
    • proto: Specifies the VPN protocol to use. It also overrides any protocol defined in provider node.
    • user: It's the username to access the VPN. It also overrides any user defined in provider node.
    • password: It's the password associated to the username. It also overrides any password defined in provider node.
XML
<!-- VpnScripter configuration file © 2016 Federico Di Marco

Provider defines the vpn providers you have. 
Each vpn provider has usually a set of servers to which you
can connect and the script creates a vpn connection 
for every server you specify for every provider listed.
The created connection will have:
- Name: Server attribute (till first .) + Provider name 
if proto is auto or empty, Server + Proto + Provider name if not null.        
- Protocol: the one specified in the server element or if null 
the one specified in the provider element or if null auto
- L2tp PSK, User, Password: the one specified in the server 
element or if null the one specified in the provider element 
(you don't have to repeat them for all servers)
- Server hostname: Server attribute + Provider base domain 
if server attributes does not contain any "." char
                  otherwise server attribute
-->

<Providers>
  <Provider name="TestVPN1" 
  basedomain="myvpn.com" user="user01" 
  password="hottie">   
    <Server server="sw" />   
    <!--Vpn will have sw.myvpn.com as host address and 
    SW TestVPN1 as name, automatic protocol-->
    <Server server="ro" proto="PPTP"/>   
    <!--Server proto overrides Provider specified protocol (auto in this case)-->
    <Server server="kick-vm.myvpnext.com" 
    proto="SSTP"/>  
    <!--Vpn will have kick-vm.myvpnext.com as host address 
    and KICK-VM TestVPN1 as name -->
    <Server server="sp" proto="IKEV2" 
    user="user15" password="beer" /> 
</Provider>
<Provider name="TestVPN2" basedomain="myvpn2.com" 
l2tppsk="12345" user="test001" password="master">
    <Server server="karate" proto="L2TP" 
    l2tppsk="314pi"/> 
    <Server server="kazu"  /> 
    <Server server="moon"  /> 
    <Server server="sun" /> 
</Provider>
</Providers>

Inside the zip archive, you will find 3 files:

  • DotRas.dll: It's a .NET class library which is used by the PowerShell script to create VPN connections and it must be in the same folder of the script.
  • vpnconfig.xml: It's the above XML configuration file which must be edited with your settings.
  • VpnScripter.ps1: It is a PowerShell script which can be launched from PowerShell or double clicked. It accepts an optional parameter named ConfigFile specifying the name of the XML configuration file to use (defaults to vpnconfig.xml). You can specify the -Debug flag to obtain a more verbose log output.

The script has been tested on Windows 10, but should also work in any Windows with at least PowerShell 3.0.

Implementation Overview

Basically, all the code is inside a single PowerShell file called VpnScripter.ps1, which contains a mixture of XSD, C# and PowerShell code. Basically it:

  • Parses and verifies the XML configuration file vpnconfig.xml using an embedded XSD schema defined inside the PowerShell script.
    The schema itself is very simple (e.g. contains mainly xs:attribute clauses specifying the allowed/required attributes of the configuration file) and defines a simple type called NotEmptyTrimmedString which, as the name implies, checks by a regular expression that an attribute value is a "not empty trimmed string" (it is used for server attribute which must be "meaningful"). The verification of XSD schema is performed through a PowerShell function ValidateLoadXml which uses standard .NET functions to perform validation (e.g., C# new XmlDocument().Load(XmlReader.Create(<file>,<settings with schema file>)))
    C#
    function ValidateLoadXml([string] $XmlFile, [string] $Schema) {
    
    	$verr={ 
    		Write-Error "Error: malformed XSD/XML Line: $($_.Exception.LineNumber) 
    		Offset: $($_.Exception.LinePosition) - $($_.Message)" 
    		throw [System.IO.InvalidDataException] 
    	}
    
    	try {
    		[System.Xml.XmlReaderSettings]$readsett=New-Object System.Xml.XmlReaderSettings
    		$readsett.Schemas.Add([System.Xml.Schema.XmlSchema]::Read
    		((New-Object System.IO.StringReader($Schema)),$verr))
    		$readsett.ValidationType=[System.Xml.ValidationType]::Schema
    		$readsett.add_ValidationEventHandler($verr)
    		$xmlconf = New-Object System.Xml.XmlDocument
    		$xmlconf.Load([System.Xml.XmlReader]::Create($XmlFile,$readsett))
    	}
    	catch [System.IO.InvalidDataException]  {
    		return $null
    	}
    	
    	return $xmlconf
    }	
  • Cycles for all Server nodes of all the Provider nodes of the XML configuration file
    • Builds the list of parameters needed to create the VPN entry in Windows according to aforementioned rules (if server attribute contains a point '.', it's the full DNS name of the VPN endpoint, otherwise it must be concatenated with the basedomain attribute, etc.). In order to evaluate the needed user, password, protocol and l2tppsk from the information contained in Server and Provider nodes, the script uses a coalescing operator, quite common in SQL queries and C# (operator ??), but unluckily missing in PowerShell, hence implemented in custom defined PowerShell function called Coalesce.
      PowerShell
      function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
        if ($EmptyStringAsNull.IsPresent) {
          return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
        }
        else {
          return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
        }  
      }	
    • Performs additional checks on parameters which are not so straightforward to do with XSD:
      • If proto attribute is L2TP, the l2tppsk attribute must be not empty
      • The user and password attributes must not be empty
    • Checks if the VPN entry already exists in Windows through Get-VpnConnection cmdlet and if so deletes it through Remove-VpnConnection cmdlet.
      PowerShell
      $exist=Get-VpnConnection -Name $vpnname -ErrorAction silentlycontinue
      if ($exist -ne $null) {
          Write-Host "Info: Removing VPN connection $vpnname"
      
          Remove-VpnConnection -Name $vpnname -Force
      }
      
    • Creates a new VPN entry by calling a custom defined C# function called Add.
      Basically, this function sets up the VPN parameters (proto is converted into an enum by a simple conversion function called ConvertProto, with an IKEV2 VPN EAP authentication must be used, etc.) and straightforwardly calls the DotRas.dll functions to add a new VPN entry to Windows.
      C#
      public static void Add(string path,string name,string server,
      string proto, string l2tppsk, string user, string password) {
          RasPhoneBook PhoneBook=new RasPhoneBook();
          PhoneBook.Open(path);
          RasEntry VpnEntry = RasEntry.CreateVpnEntry
          (name,server, ConvertProto(proto),
          RasDevice.Create(name, DotRas.RasDeviceType.Vpn), true);
          VpnEntry.Options.UsePreSharedKey = true;
          VpnEntry.Options.CacheCredentials = true;
          VpnEntry.Options.ReconnectIfDropped = true;
          if (VpnEntry.VpnStrategy==RasVpnStrategy.IkeV2Only) {
              // 23 EAP-AKA
              // 50 EAP-AKA'
              // 18 EAP-SIM
              // 21 EAP-TTLS
              // 25 PEAP
              // 26 EAP-MSCHAPV2
              // 13 EAP-smart card or certificate
              VpnEntry.Options.RequireEap = true;
              VpnEntry.CustomAuthKey=26; // 26 means eap-mschapv2 username/password
          }
          else {
              VpnEntry.Options.RequireMSChap2 = true;
          }
          //VpnEntry.Options.RequireWin95MSChap = false; // seems to be ignored,
          //chap is still checked in newly created vpn profile
          //VpnEntry.Options.RequireMSChap = false;  // seems to be ignored,
          //chap is still checked in newly created vpn profile
          //VpnEntry.Options.RequireChap = false; // seems to be ignored,
          //chap is still checked in newly created vpn profile
          VpnEntry.EncryptionType = RasEncryptionType.RequireMax;
          PhoneBook.Entries.Add(VpnEntry);
          VpnEntry.UpdateCredentials(RasPreSharedKey.Client,l2tppsk);
          VpnEntry.UpdateCredentials(new System.Net.NetworkCredential(user,password));
      }
      

As already said, both the XSD schema and the .NET helper code have been embedded inside the PowerShell script in a single, almost self-contained (needs DotRas.dll) file. In fact, PowerShell scripts have the nice feature to allow the inclusion of any .NET code inside allowing them to do everything that can be done with C# code. It's interesting the way in which a C# code can be parsed and compiled:

  • First "load" any DLL (with Add-Type -Path <dll file>) or assembly (with Add-Type -AssemblyName <assembly>) used by the custom code
  • Then parse and compile your custom C# code with
    Add-Type -ReferencedAssemblies <list of referenced assemblies> -TypeDefinition <string containing C# code> -Language CSharp
PowerShell
Add-Type -Path $psscriptroot\DotRas.dll
Add-Type -AssemblyName System.Xml
Add-Type -ReferencedAssemblies $psscriptroot\DotRas.dll,System.Xml 
         -TypeDefinition $Source -Language CSharp

Points of Interest

The external library DotRas.dll has been used because there does not seem to be a way to configure credentials (login and password) with the standard PowerShell command Add-VpnConnection, please let me know if you find a way.

Repository

You can find the source code repository on GitHub. If you want to add a predefined configuration file for your VPN provider with all servers or to improve the code, you are welcome.

History

  • V1.0 (2nd December 2016
    • First release
  • V1.1. (7th December 2016)
    • Added override of l2tppsk, user and password in server nodes
    • Added XML validation through XSD and simple checks on user/password/l2tppsk parameters
  • V1.2 (27th March 2017)
    • Added "Implementation overview" section

References

  • [1] PPP is an old point-to-point protocol historically used to connect to ISP by modems. It allows to establish a connection between two endpoints.
  • [2] IPSEC is an encrypted tunnel between two endpoints based on a set of rules (called security associations) establishing the encryption and integrity algorithms to be used in the tunnel and the network packets which need to be tunnelled (e.g. all TCP packets, TCP addressed to a particular port, etc.). Basically, it encapsulates TCP/UDP in a new encrypted IP packet (ESP). The algorithms used to encrypt and to check message's integrity are established through a negotiation protocol called IKE. Since client and server mainly authenticate mutually by certificates, IPSEC is usually used in lan-to-lan VPN (e.g. connecting two different organizations' network) and not in a client-to-lan scenario, however for this kind of scenario, it is possible to authenticate through a PSK (pre-shared key or shared secret), although this entails a lower security.
  • [3] Microsoft VPN Tunnelling Protocols

License

This article, along with any associated source code and files, is licensed under The MIT License