diff --git a/.github/workflows/DevelopmentDPC.yml b/.github/workflows/DevelopmentDPC.yml
index 26190ec..3f70424 100644
--- a/.github/workflows/DevelopmentDPC.yml
+++ b/.github/workflows/DevelopmentDPC.yml
@@ -7,9 +7,11 @@ name: Development DPC
on:
push:
- branches: [ "Development" ]
+ branches-ignore:
+ - main
pull_request:
- branches: [ "Development" ]
+ branches-ignore:
+ - main
jobs:
build:
diff --git a/DPCInstaller/ADMX/dpc.admx b/DPCInstaller/ADMX/dpc.admx
index c6e8712..483e654 100644
--- a/DPCInstaller/ADMX/dpc.admx
+++ b/DPCInstaller/ADMX/dpc.admx
@@ -1335,7 +1335,7 @@
-
+
-
diff --git a/DPCInstaller/ADMX/en-US/DPC.adml b/DPCInstaller/ADMX/en-US/DPC.adml
index cfa3041..b943893 100644
--- a/DPCInstaller/ADMX/en-US/DPC.adml
+++ b/DPCInstaller/ADMX/en-US/DPC.adml
@@ -255,11 +255,15 @@ If PAC File is selected then the full url to the PAC file should be defined in t
Example:
http://proxy.contoso.com/vpnproxy.pac
+https://proxy.contoso.com/vpnproxy.pac
+http://proxy.contoso.com:8080/vpnproxy.pac
+https://proxy.contoso.com:8081/vpnproxy.pac
If Proxy Server is selected then the hostname and port of the proxy server should be defined in the Proxy Value
Example:
-http://proxy.contoso.com:8080
+proxy.contoso.com
+proxy.contoso.com:8080
NOTE: Proxy Exclusions and Bypass For Local Address Options are only valid when 'Proxy Server' is selected. These settings can be set with a seperate Group Policy Setting
diff --git a/DPCInstaller/ProductVersion.wxi b/DPCInstaller/ProductVersion.wxi
index d517415..4b74044 100644
--- a/DPCInstaller/ProductVersion.wxi
+++ b/DPCInstaller/ProductVersion.wxi
@@ -1,3 +1,3 @@
-
+
diff --git a/DPCLibrary/Models/IPv4Address.cs b/DPCLibrary/Models/IPv4Address.cs
index 82a23ff..6d4713f 100644
--- a/DPCLibrary/Models/IPv4Address.cs
+++ b/DPCLibrary/Models/IPv4Address.cs
@@ -40,7 +40,7 @@ public void LoadFromString(string address)
throw new ArgumentException("IPAddress must not be null");
}
- if (!Validate.IPv4(address))
+ if (!Validate.IPv4Address(address))
{
throw new ArgumentException("IPAddress must be a valid IPv4 Address");
}
diff --git a/DPCLibrary/Models/IPv6Address.cs b/DPCLibrary/Models/IPv6Address.cs
index 5fce82a..48a40d8 100644
--- a/DPCLibrary/Models/IPv6Address.cs
+++ b/DPCLibrary/Models/IPv6Address.cs
@@ -81,7 +81,7 @@ public void LoadFromString(string address)
throw new ArgumentException("IPAddress must not be null");
}
- if (!Validate.IPv6(address))
+ if (!Validate.IPv6Address(address))
{
throw new ArgumentException("IPAddress must be a valid IPv6 Address");
}
diff --git a/DPCLibrary/Models/Route.cs b/DPCLibrary/Models/Route.cs
index ca58ed8..4b16c5e 100644
--- a/DPCLibrary/Models/Route.cs
+++ b/DPCLibrary/Models/Route.cs
@@ -72,28 +72,35 @@ public Route(XElement routeNode)
string tempAddress = routeNode.XPathSelectElement("Address")?.Value;
if (tempAddress.Contains("/"))
{
- string[] tempAddressSplit = tempAddress.Split('/');
- tempAddress = tempAddressSplit[0];
- Prefix = Convert.ToInt32(tempAddressSplit[1], CultureInfo.InvariantCulture);
+ Prefix = IPUtils.GetIPCIDRSuffix(tempAddress);
+ tempAddress = IPUtils.GetIPAddress(tempAddress);
}
else
{
Prefix = Convert.ToInt32(routeNode.XPathSelectElement("PrefixSize")?.Value, CultureInfo.InvariantCulture);
}
- if (Validate.IPv4(tempAddress))
+ if (Validate.IPv4Address(tempAddress))
{
Address = new IPv4Address();
Address.LoadFromString(tempAddress);
+ if (Prefix < 0 || Prefix > 32)
+ {
+ throw new InvalidDataException("IPv4 Prefix " + Prefix + " was not considered a valid CIDR Suffix");
+ }
}
- else if (Validate.IPv6(tempAddress))
+ else if (Validate.IPv6Address(tempAddress))
{
Address = new IPv6Address();
Address.LoadFromString(tempAddress);
+ if (Prefix < 0 || Prefix > 128)
+ {
+ throw new InvalidDataException("IPv6 Prefix " + Prefix + " was not considered a valid CIDR Suffix");
+ }
}
else
{
- throw new InvalidDataException("IP Address " + tempAddress + " was not considered a valid Iv4 or IPv6 Address");
+ throw new InvalidDataException("IP Address " + tempAddress + " was not considered a valid IPv4 or IPv6 Address");
}
diff --git a/DPCLibrary/Utils/AccessWMI.cs b/DPCLibrary/Utils/AccessWMI.cs
index 6427c20..fd29a42 100644
--- a/DPCLibrary/Utils/AccessWMI.cs
+++ b/DPCLibrary/Utils/AccessWMI.cs
@@ -169,6 +169,7 @@ private static string SanitizeName(string name)
private static string Sanitize(string profileData)
{
+ profileData = profileData.Replace("&", "&"); //Must be first or breaks all the other escape codes
profileData = profileData.Replace("<", "<");
profileData = profileData.Replace(">", ">");
profileData = profileData.Replace("\"", """);
@@ -232,7 +233,15 @@ public static bool SetProxyExcludeExceptions(string profileName, IList e
return false;
}
- List proxyExcludeList = new List(excludeList);
+ List proxyExcludeList;
+ if (excludeList != null)
+ {
+ proxyExcludeList = new List(excludeList);
+ }
+ else
+ {
+ proxyExcludeList = new List();
+ }
using (CimOperationOptions options = GetContextOptions(DeviceInfo.SYSTEMSID, cancelToken))
{
@@ -244,7 +253,7 @@ public static bool SetProxyExcludeExceptions(string profileName, IList e
CimMethodParameter.Create("BypassProxyForLocal", bypassForLocal, CimFlags.In)
})
{
- if (excludeList.Count > 0)
+ if (proxyExcludeList.Count > 0)
{
string proxyServer = GetProxyServer(profileName, cancelToken);
if (!string.IsNullOrWhiteSpace(proxyServer))
@@ -277,7 +286,7 @@ private static IList GetMachineCertificateEKUFilter(string profileName,
return EKUData.ToList();
}
- private static IList GetProxyExcludeList(string profileName, CancellationToken cancelToken)
+ public static IList GetProxyExcludeList(string profileName, CancellationToken cancelToken)
{
IList profileDetailsList = new List();
string XML = GetWMIVPNConfig(profileName, cancelToken);
diff --git a/DPCLibrary/Utils/IPUtils.cs b/DPCLibrary/Utils/IPUtils.cs
index 0a5e2c5..813c047 100644
--- a/DPCLibrary/Utils/IPUtils.cs
+++ b/DPCLibrary/Utils/IPUtils.cs
@@ -46,26 +46,26 @@ public static int GetIPCIDRSuffix(string address)
}
string[] splitAddress = address.Split('/');
- if (splitAddress.Length == 1 && Validate.IPv4(address))
+ if (splitAddress.Length == 1 && Validate.IPv4EndpointAddress(address))
{
//If there is no CIDR then assume it is a single IPv4 hence /32
return 32;
}
- if (splitAddress.Length == 1 && Validate.IPv6(address))
+ if (splitAddress.Length == 1 && Validate.IPv6EndpointAddress(address))
{
//If there is no CIDR then assume it is a single IPv6 hence /128
return 128;
}
else if (splitAddress.Length == 2)
{
- if (Validate.IPv4(splitAddress[1]))
+ if (Validate.IPv4Address(splitAddress[1]))
{
//Mask may be in the format 255.255.0.0
return GetCIDRFromNetMask(splitAddress[1]);
}
else
{
- //Assume mask is in the format /24
+ //Assume mask is in the CIDR format
//Throw exception if not valid
return int.Parse(splitAddress[1], CultureInfo.InvariantCulture);
}
diff --git a/DPCLibrary/Utils/VPNProfileCreator.cs b/DPCLibrary/Utils/VPNProfileCreator.cs
index 11ce4d4..c805412 100644
--- a/DPCLibrary/Utils/VPNProfileCreator.cs
+++ b/DPCLibrary/Utils/VPNProfileCreator.cs
@@ -479,8 +479,15 @@ public void LoadFromRegistry()
//Proxy Disabled, reset proxy settings to defaults
ProxyType = ProxyType.None;
ProxyValue = "";
- if (ProxyExcludeList != null && ProxyExcludeList.Count > 0)
+ //Initialise the value if it wasn't already
+ if (ProxyExcludeList == null)
{
+ ProxyExcludeList = new List();
+ }
+
+ if (ProxyExcludeList.Count > 0)
+ {
+ //Proxy has been disabled, clear out any previous proxy exclusions as they are no longer needed
ProxyExcludeList.Clear();
}
ProxyBypassForLocal = false;
@@ -1216,9 +1223,21 @@ public void Generate()
{
writer.WriteComment(Route.Value);
}
+ string address = IPUtils.GetIPAddress(Route.Key);
+ if (string.IsNullOrWhiteSpace(address))
+ {
+ ValidationFailures.AppendLine("Route: " + Route.Key + " is not valid and has been skipped");
+ continue;
+ }
+ int prefix = IPUtils.GetIPCIDRSuffix(Route.Key);
+ if (prefix < 0)
+ {
+ ValidationFailures.AppendLine("Route: " + Route.Key + " is not valid and has been skipped");
+ continue;
+ }
writer.WriteStartElement("Route");
- writer.WriteElementString("Address", IPUtils.GetIPAddress(Route.Key));
- writer.WriteElementString("PrefixSize", IPUtils.GetIPCIDRSuffix(Route.Key).ToString(CultureInfo.InvariantCulture));
+ writer.WriteElementString("Address", address);
+ writer.WriteElementString("PrefixSize", prefix.ToString(CultureInfo.InvariantCulture));
if (RouteMetric > 0)
{
writer.WriteElementString("Metric", RouteMetric.ToString(CultureInfo.InvariantCulture));
@@ -1838,7 +1857,7 @@ private static List GetOffice365ExcludeRoutes()
{
if (ipList.Contains(item)) continue;
//Don't add IPv6 addresses as currently windows won't connect the profile if a client doesn't have an IPv6 address and there are IPv6 routes in the Route Table
- if (Validate.IPv4(item) || Validate.IPv4CIDR(item))
+ if (Validate.IPv4EndpointAddress(item) || Validate.IPv4CIDR(item))
{
ipList.Add(item);
}
@@ -1887,7 +1906,7 @@ private static string GetDNSRoutes(ref Dictionary resolvedIPList
}
//Don't add IPv6 addresses as IPv6 Exclusions added to a machine without an IPv6 address breaks the tunnel completely
//if (Validate.IPv4(item) || Validate.IPv6(item))
- if (Validate.IPv4(item.Key))
+ if (Validate.IPv4EndpointAddress(item.Key))
{
resolvedIPList.Add(item.Key, item.Value);
}
diff --git a/DPCLibrary/Utils/Validate.cs b/DPCLibrary/Utils/Validate.cs
index 8bf3396..af097db 100644
--- a/DPCLibrary/Utils/Validate.cs
+++ b/DPCLibrary/Utils/Validate.cs
@@ -1,64 +1,40 @@
using System.IO;
-using System.Text.RegularExpressions;
+using System.Linq;
using System.Net;
using System.Net.Sockets;
+using System.Text.RegularExpressions;
namespace DPCLibrary.Utils
{
public static class Validate
{
+ ///
+ /// Validates if the address is either a valid IPv4 or IPv6 endpoint address, or a valid IPv4 or IPv6 CIDR address
+ ///
+ /// The address to validate
public static bool IPv4OrIPv6OrCIDR(string address)
{
- if (string.IsNullOrWhiteSpace(address))
- {
- return false;
- }
- //If address is either ipv4 or ipv6 in either IP or CIDR format allow it
- if (IPv4OrCIDR(address) || IPv6OrCIDR(address))
- {
- return true;
- }
- else
- {
- return false;
- }
+ return ValidateIPInternal(address, AddressFamily.InterNetwork, true, false) ||
+ ValidateIPInternal(address, AddressFamily.InterNetworkV6, true, false);
}
+ ///
+ /// Validates if the address is either a valid IPv6 endpoint address, or a valid IPv6 CIDR address
+ ///
+ /// The address to validate
public static bool IPv6OrCIDR(string address)
{
- if (string.IsNullOrWhiteSpace(address))
- {
- return false;
- }
-
- string[] SplitCIDR = address.Split('/');
-
- if (SplitCIDR.Length == 1)
- {
- return IPv6(address);
- }
- else
- {
- return IPv6CIDR(address);
- }
+ return ValidateIPInternal(address, AddressFamily.InterNetworkV6, true, false);
}
+ ///
+ /// Validates if the address is either a valid IPv4 endpoint address, or a valid IPv4 CIDR address
+ ///
+ ///
+ ///
public static bool IPv4OrCIDR(string address)
{
- if (string.IsNullOrWhiteSpace(address))
- {
- return false;
- }
-
- string[] SplitCIDR = address.Split('/');
- if (SplitCIDR.Length == 1)
- {
- return IPv4(address);
- }
- else
- {
- return IPv4CIDR(address);
- }
+ return ValidateIPInternal(address, AddressFamily.InterNetwork, true, false);
}
//Ensure that comments don't have --> which could really mess up the XML as it will cancel the comment and allow arbitrary XML injection
@@ -85,7 +61,7 @@ public static bool IPAddressCommaList(string IPList)
//Invalidate the entire record if there is 1 invalid IP
foreach (string ip in ipSplitList)
{
- if (!IPv4(ip.Trim()))
+ if (!IPv4EndpointAddress(ip.Trim()))
{
return false;
}
@@ -94,125 +70,181 @@ public static bool IPAddressCommaList(string IPList)
return true;
}
- public static bool IPv6(string address)
+ ///
+ /// Validates if the address is a valid IPv6 endpoint address
+ ///
+ ///
+ ///
+ public static bool IPv6EndpointAddress(string address)
+ {
+ return ValidateIPInternal(address, AddressFamily.InterNetworkV6, false, false);
+ }
+
+ ///
+ /// Validates if the address is a valid IPv6 endpoint address or :: (unspecified address)
+ ///
+ ///
+ ///
+ public static bool IPv6Address(string address)
+ {
+ return ValidateIPInternal(address, AddressFamily.InterNetworkV6, false, true);
+ }
+
+ ///
+ /// Validates if the address is a valid IPv4 endpoint address
+ ///
+ ///
+ ///
+ public static bool IPv4EndpointAddress(string address)
+ {
+ return ValidateIPInternal(address, AddressFamily.InterNetwork, false, false);
+ }
+
+ ///
+ /// Validates if the address is a valid IPv4 endpoint address or 0.0.0.0
+ ///
+ ///
+ ///
+ public static bool IPv4Address(string address)
+ {
+ return ValidateIPInternal(address, AddressFamily.InterNetwork, false, true);
+ }
+
+ ///
+ /// Validates if the address is a valid IP address of the specified family, with options for CIDR and unspecified address
+ ///
+ /// The string representation of the address to validate
+ /// The expected address family (IPv4 or IPv6)
+ /// Specifies if CIDR notation is allowed
+ /// Specifies if the unspecified address (0.0.0.0 or ::) is allowed
+ ///
+ private static bool ValidateIPInternal(string address, AddressFamily addressFamily, bool allowCidr, bool allowUnspecifiedAddress)
{
if (string.IsNullOrWhiteSpace(address))
{
return false;
}
- if (address == "::")
+ IPAddress anyAddress;
+
+ if (addressFamily == AddressFamily.InterNetwork)
{
- return false; //reject simple IPv6 Zero
+ anyAddress = IPAddress.Any;
}
-
- //Find and reject other ways of making IPv6 Zero
- Match zeroResult = Regex.Match(address, "^s*(((0{1,4}:){7}(0{1,4}|:))|((0{1,4}:){6}|(:))|((0{1,4}:){5}(((:0]{1,4}){1,2})|:))|((0{1,4}:){4}(((:0{1,4}){1,3})|((:0{1,4})?:)|:))|((0{1,4}:){3}(((:0{1,4}){1,4})|((:0{1,4}){0,2}:)|:))|((0{1,4}:){2}(((:0{1,4}){1,5})|((:0{1,4}){0,3}:)|:))|((0{1,4}:){1}(((:0{1,4}){1,6})|((:0{1,4}){0,4}:)|:))|(:(((:0{1,4}){1,7})|((:0{1,4}){0,5}:)|:)))(%.+)?s*");
- if (zeroResult.Value == address)
+ else if (addressFamily == AddressFamily.InterNetworkV6)
{
- return false;
+ anyAddress = IPAddress.IPv6Any;
}
-
- //Check that the IPAddress class accepts the value
- if (!IPAddress.TryParse(address, out IPAddress potentialv6))
+ else
{
return false;
}
- //Checks that the address is actually considered IPv6 and not IPv4 etc
- return potentialv6.AddressFamily == AddressFamily.InterNetworkV6;
- }
+ string[] addressParts = address.Split('/');
+ bool isCidr = addressParts.Length == 2;
+ int prefix = -1;
- public static bool IPv4(string address)
- {
- if (string.IsNullOrWhiteSpace(address))
+ if (isCidr)
{
- return false;
- }
+ if (!allowCidr)
+ {
+ return false;
+ }
- if (address == "0.0.0.0")
- {
- return false; //reject all 0s as its only valid as a CIDR
- }
+ if (!int.TryParse(addressParts[1], out prefix))
+ {
+ // Unable to turn second part into int
+ return false;
+ }
- Match result = Regex.Match(address, "\\b(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}\\b");
- return result.Value == address;
- }
+ if (addressFamily == AddressFamily.InterNetwork && (prefix < 0 || prefix > 32))
+ {
+ // Not a valid IPv4 CIDR prefix
+ return false;
+ }
- public static bool IPv6CIDR(string address)
- {
- if (string.IsNullOrWhiteSpace(address))
- {
- return false;
+ if (addressFamily == AddressFamily.InterNetworkV6 && (prefix < 0 || prefix > 128))
+ {
+ // Not a valid IPv6 CIDR prefix
+ return false;
+ }
}
- string[] SplitCIDR = address.Split('/');
- if (SplitCIDR.Length != 2)
+ if (!IPAddress.TryParse(addressParts[0], out IPAddress ipAddress))
{
+ // Unable to turn first part into IP
return false;
}
- if (!IPv6(SplitCIDR[0]))
+ if (ipAddress.AddressFamily != addressFamily)
{
- //Front part isn't a valid address
+ // Not the correct address family
return false;
}
- if (!int.TryParse(SplitCIDR[1], out int CIDRVal))
+ if (anyAddress.Equals(ipAddress) && !(allowUnspecifiedAddress || (isCidr && allowCidr)))
{
- //Unable to turn second part into int
+ // The unspecified address is only valid if allowAnyAddress is true or if it's a CIDR and allowCidr is true
return false;
}
- if (CIDRVal > 128)
+ if (prefix == 0 && !anyAddress.Equals(ipAddress))
{
+ // A CIDR of 0 is only valid if the IP is the unspecified address
return false;
}
- if (CIDRVal <= 0)
+ if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
- return false;
+ // v4 specific validations
+
+ if (address?.Count(c => c == '.') != 3)
+ {
+ return false; // There's a few unit tests for `10.0.0` which are valid shorthand IPs, but adding this check to ensure there are 4 octets for consistency with previous behaviour
+ }
}
return true;
}
- public static bool IPv4CIDR(string address)
+ ///
+ /// Validates if the address is a valid IPv6 CIDR address
+ ///
+ ///
+ ///
+ public static bool IPv6CIDR(string address)
{
if (string.IsNullOrWhiteSpace(address))
{
return false;
}
- string[] SplitCIDR = address.Split('/');
- if (SplitCIDR.Length != 2)
+ if (!address.Contains("/"))
{
return false;
}
- if (!IPv4(SplitCIDR[0]))
- {
- //Front part isn't a valid address
- return false;
- }
-
- if (!int.TryParse(SplitCIDR[1], out int CIDRVal))
- {
- //Unable to turn second part into int
- return false;
- }
+ return ValidateIPInternal(address, AddressFamily.InterNetworkV6, true, true);
+ }
- if (CIDRVal > 32)
+ ///
+ /// Validates if the address is a valid IPv4 CIDR address
+ ///
+ ///
+ ///
+ public static bool IPv4CIDR(string address)
+ {
+ if (string.IsNullOrWhiteSpace(address))
{
return false;
}
- if (CIDRVal <= 0)
+ if (!address.Contains("/"))
{
return false;
}
- return true;
+ return ValidateIPInternal(address, AddressFamily.InterNetwork, true, true);
}
public static string Thumbprint(string potentialThumbprint)
@@ -386,8 +418,8 @@ public static bool PortList(string list)
foreach (string PortElement in PortElements)
{
string element = PortElement.Trim();
- string[] portRange = element.Split(new char[]{ '-' }, 2);
- foreach(string portRangeElement in portRange)
+ string[] portRange = element.Split(new char[] { '-' }, 2);
+ foreach (string portRangeElement in portRange)
{
string rangeElement = portRangeElement.Trim();
if (string.IsNullOrEmpty(rangeElement))
@@ -444,7 +476,7 @@ public static bool PackageId(string id)
}
Match result = Regex.Match(id, "^[a-zA-Z0-9.-]+[_][a-zA-Z0-9]+");
- if(result.Value == id)
+ if (result.Value == id)
{
return true;
}
@@ -461,4 +493,4 @@ public static bool PackageId(string id)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/DPCLibraryTests/ValidateTests.cs b/DPCLibraryTests/ValidateTests.cs
index 076de47..ad20d54 100644
--- a/DPCLibraryTests/ValidateTests.cs
+++ b/DPCLibraryTests/ValidateTests.cs
@@ -29,9 +29,10 @@ public class ValidateTests
[DataRow("1.1.1.1/-1")]
[DataRow("1.1.1.1/33")]
[DataRow("1.1.1.1/bob")]
- [DataRow("2001:0db9::1/64")]
- [DataRow("2001:0db9::1")]
[DataRow("192.168..1/32")]
+ [DataRow("192.168.1.1/0")]
+ [DataRow("192.168.1.1/35")]
+ [DataRow("0.0.0.0")]
public void InvalidIpv4ORCIDR(string IPv4Address)
{
bool result = Validate.IPv4OrCIDR(IPv4Address);
@@ -50,6 +51,8 @@ public void InvalidIpv4ORCIDR(string IPv4Address)
[DataRow("192.168.0.0/16")]
[DataRow("192.168.1.1/32")]
[DataRow("20.1.2.3/32")]
+ [DataRow("0.0.0.0/1")]
+ [DataRow("0.0.0.0/0")]
public void ValidIpv4ORCIDR(string IPv4Address)
{
bool result = Validate.IPv4OrCIDR(IPv4Address);
@@ -80,9 +83,9 @@ public void ValidIpv4ORCIDR(string IPv4Address)
[DataRow("2001:0db9::1")]
[DataRow("0.0.0.0/0")]
[DataRow("0.0.0.0")]
- public void InvalidIpv4(string IPv4Address)
+ public void InvalidIpv4EndpointAddress(string IPv4Address)
{
- bool result = Validate.IPv4(IPv4Address);
+ bool result = Validate.IPv4EndpointAddress(IPv4Address);
Assert.IsFalse(result);
}
@@ -92,9 +95,22 @@ public void InvalidIpv4(string IPv4Address)
[DataRow("192.168.5.4")]
[DataRow("255.255.255.255")]
[DataRow("20.1.2.3")]
- public void ValidIpv4(string IPv4Address)
+ public void ValidIpv4EndpointAddress(string IPv4Address)
{
- bool result = Validate.IPv4(IPv4Address);
+ bool result = Validate.IPv4EndpointAddress(IPv4Address);
+ Assert.IsTrue(result);
+ }
+
+ [DataTestMethod]
+ [DataRow("10.0.0.0")]
+ [DataRow("172.16.35.3")]
+ [DataRow("192.168.5.4")]
+ [DataRow("255.255.255.255")]
+ [DataRow("20.1.2.3")]
+ [DataRow("0.0.0.0")]
+ public void ValidIpv4Address(string IPv4Address)
+ {
+ bool result = Validate.IPv4Address(IPv4Address);
Assert.IsTrue(result);
}
@@ -118,6 +134,7 @@ public void ValidIpv6CIDR(string IPv6Address)
[DataRow("10.32.99.0/24")]
[DataRow("192.168.0.0/16")]
[DataRow("192.168.1.1/32")]
+ [DataRow("2001::1/129")]
public void InvalidIpv6CIDR(string IPv6Address)
{
bool result = Validate.IPv6CIDR(IPv6Address);
@@ -150,23 +167,16 @@ public void InvalidIpv6CIDR(string IPv6Address)
[DataRow("255.255.255.255")]
[DataRow("20.1.2.3")]
[DataRow("0.0.0.0")]
- [DataRow("10.0.0.0")]
- [DataRow("172.16.35.3")]
- [DataRow("192.168.5.4")]
- [DataRow("255.255.255.255")]
- [DataRow("20.1.2.3")]
[DataRow("172.16.0.0/12")]
- [DataRow("10.32.99.0/24")]
[DataRow("192.168.0.0/16")]
[DataRow("192.168.1.1/32")]
[DataRow("20.1.2.3/32")]
[DataRow("00:00:00:00:00:00:00:00")]
- [DataRow("00:00:00:00:00:00:00:00/8")]
[DataRow("0:0:0:0:0:0:0:0")]
[DataRow("0:00:000:0000:0000:000:00:0")]
- [DataRow("0:00:000:0000:0000:000:00:0/0")]
[DataRow("0::0")]
- [DataRow("::/0")]
+ [DataRow("2001:0db9::1/0")]
+ [DataRow("2001::1/129")]
public void InvalidIpv6ORCIDR(string IPv4Address)
{
bool result = Validate.IPv6OrCIDR(IPv4Address);
@@ -180,9 +190,12 @@ public void InvalidIpv6ORCIDR(string IPv4Address)
[DataRow("2001:0db9::1/64")]
[DataRow("2001:0db9::1/128")]
[DataRow("2001:0db9:c9::/128")]
- public void ValidIpv6ORCIDR(string IPv4Address)
+ [DataRow("::/0")]
+ [DataRow("0:00:000:0000:0000:000:00:0/0")]
+ [DataRow("00:00:00:00:00:00:00:00/8")]
+ public void ValidIpv6ORCIDR(string IPv6Address)
{
- bool result = Validate.IPv6OrCIDR(IPv4Address);
+ bool result = Validate.IPv6OrCIDR(IPv6Address);
Assert.IsTrue(result);
}
@@ -203,6 +216,8 @@ public void ValidIpv6ORCIDR(string IPv4Address)
[DataRow("192.168.0.0/16")]
[DataRow("192.168.1.1/32")]
[DataRow("20.1.2.3/32")]
+ [DataRow("0.0.0.0/1")]
+ [DataRow("0.0.0.0/0")]
public void ValidIpv4ORIpv6ORCIDR(string IPv4Address)
{
bool result = Validate.IPv4OrIPv6OrCIDR(IPv4Address);
@@ -244,9 +259,10 @@ public void ValidIpv4ORIpv6ORCIDR(string IPv4Address)
[DataRow("0:00:000:0000:0000:000:00:0/0")]
[DataRow("0::0")]
[DataRow("::/0")]
- public void InvalidIpv6(string IPv6Address)
+ [DataRow("2001:0db9::1/129")]
+ public void InvalidIpv6EndpointAddress(string IPv6Address)
{
- bool result = Validate.IPv6(IPv6Address);
+ bool result = Validate.IPv6EndpointAddress(IPv6Address);
Assert.IsFalse(result);
}
@@ -256,12 +272,26 @@ public void InvalidIpv6(string IPv6Address)
[DataRow("1:2:3:4:5:6:7:8")]
[DataRow("1111:2222:3333:4444:5555:6666:7777:8888")]
[DataRow("2001:0db9::ac11:c9")]
- public void ValidIpv6(string IPv6Address)
+ public void ValidIpv6EndpointAddress(string IPv6Address)
{
- bool result = Validate.IPv6(IPv6Address);
+ bool result = Validate.IPv6EndpointAddress(IPv6Address);
Assert.IsTrue(result);
}
+ [DataTestMethod]
+ [DataRow("2001:0db9::1")]
+ [DataRow("2001::1")]
+ [DataRow("1:2:3:4:5:6:7:8")]
+ [DataRow("1111:2222:3333:4444:5555:6666:7777:8888")]
+ [DataRow("2001:0db9::ac11:c9")]
+ [DataRow("::")]
+ public void ValidIpv6Address(string IPv6Address)
+ {
+ bool result = Validate.IPv6Address(IPv6Address);
+ Assert.IsTrue(result);
+ }
+
+
[DataTestMethod]
[DataRow("")] //Empty
[DataRow(null)] //null
@@ -424,6 +454,8 @@ public void InvalidIpv4CIDR(string IPv4Address)
[DataRow("192.168.0.0/16")]
[DataRow("192.168.1.1/32")]
[DataRow("20.1.2.3/32")]
+ [DataRow("0.0.0.0/1")]
+ [DataRow("0.0.0.0/0")]
public void ValidIpv4CIDR(string IPv4Address)
{
bool result = Validate.IPv4CIDR(IPv4Address);
diff --git a/DPCManagement/Scripts/Run-AsSYSTEM.ps1 b/DPCManagement/Scripts/Run-AsSYSTEM.ps1
index 5ed29da..5811945 100644
--- a/DPCManagement/Scripts/Run-AsSYSTEM.ps1
+++ b/DPCManagement/Scripts/Run-AsSYSTEM.ps1
@@ -1,9 +1,20 @@
-$PSExecPath = "C:\Program Files\WindowsApps\Microsoft.SysinternalsSuite_2025.5.0.0_x64__8wekyb3d8bbwe\Tools\PsExec.exe"
+#$PSExecPath = "C:\Program Files\WindowsApps\Microsoft.SysinternalsSuite_2025.5.0.0_x64__8wekyb3d8bbwe\Tools\PsExec.exe"
$VSPath = "C:\Program Files\Microsoft Visual Studio\2022\community"
$MSBuildpath = "$VSPath\MSBuild\Current\Bin\msbuild.exe"
$SolutionRootPath = "C:\source\DPC"
$Project = "DPCService"
+if ([String]::IsNullOrWhiteSpace($ManualPSExecPath))
+{
+ $StorePath = Get-ChildItem -Path "C:\Program Files\WindowsApps\" | Where-Object Name -Like "Microsoft.SysinternalsSuite_*_x64_*"
+ $PSExecPath = Join-Path -Path $StorePath.FullName -ChildPath "\Tools\PsExec.exe"
+}
+else
+{
+ $PsExecPath = $ManualPSExecPath
+}
+
+
if(-NOT (Test-path -Path $PSExecPath))
{
Throw "Unable to Locate PSExec.exe"
diff --git a/DPCManagement/Scripts/Run-TestsAsSYSTEM.ps1 b/DPCManagement/Scripts/Run-TestsAsSYSTEM.ps1
index 8541bac..1c31bfa 100644
--- a/DPCManagement/Scripts/Run-TestsAsSYSTEM.ps1
+++ b/DPCManagement/Scripts/Run-TestsAsSYSTEM.ps1
@@ -1,10 +1,20 @@
-$PSExecPath = "C:\Program Files\WindowsApps\Microsoft.SysinternalsSuite_2025.2.0.0_x64__8wekyb3d8bbwe\Tools\PsExec.exe"
+#$PSExecPath = "C:\Program Files\WindowsApps\Microsoft.SysinternalsSuite_2025.2.0.0_x64__8wekyb3d8bbwe\Tools\PsExec.exe"
$VSPath = "C:\Program Files\Microsoft Visual Studio\2022\Community"
$VSTestPath = "$VSPath\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"
$MSBuildpath = "$VSPath\MSBuild\Current\Bin\msbuild.exe"
$SolutionRootPath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
$SleepBlockingProtection = $false
+if ([String]::IsNullOrWhiteSpace($ManualPSExecPath))
+{
+ $StorePath = Get-ChildItem -Path "C:\Program Files\WindowsApps\" | Where-Object Name -Like "Microsoft.SysinternalsSuite_*_x64_*"
+ $PSExecPath = Join-Path -Path $StorePath.FullName -ChildPath "\Tools\PsExec.exe"
+}
+else
+{
+ $PsExecPath = $ManualPSExecPath
+}
+
if(-NOT (Test-path -Path $PSExecPath))
{
Throw "Unable to Locate PSExec.exe"
diff --git a/DPCService/Utils/DPCServiceEvents.cs b/DPCService/Utils/DPCServiceEvents.cs
index 06e20ba..eea5f3e 100644
--- a/DPCService/Utils/DPCServiceEvents.cs
+++ b/DPCService/Utils/DPCServiceEvents.cs
@@ -363,6 +363,8 @@ internal sealed class DPCServiceEvents : EventSource
public void ProfileDebugUpdateProfileDetail(string profileName, string variable, string errorMessage) { WriteEvent(1224, profileName, variable, errorMessage); }
[Event(1225, Message = "No Corrupt PBK Files Found", Level = EventLevel.Informational, Channel = EventChannel.Debug)]
public void DebugNoCorruptPbksFound() { WriteEvent(1225); }
+ [Event(1226, Message = "Error getting Proxy Exclusions for Profile {0}: {1}\nStackTrace: {2}", Level = EventLevel.Error, Channel = EventChannel.Operational)]
+ public void ErrorGettingProxyExclusions(string profileName, string errorMessage, string stackTrace) { WriteEvent(1226, profileName, errorMessage, stackTrace); }
//Event Logs now fail to generate if additional logs are added at this point in the file, adding to the end appears to work for some reason...
#endregion 1100-1299 Profile Monitoring
diff --git a/DPCService/Utils/ProfileAction.cs b/DPCService/Utils/ProfileAction.cs
index 2b77191..6faebcd 100644
--- a/DPCService/Utils/ProfileAction.cs
+++ b/DPCService/Utils/ProfileAction.cs
@@ -485,6 +485,15 @@ public static ManagedProfile GetProfile(string profileName, ProfileType profileT
{
DPCServiceEvents.Log.ErrorGettingNetworkOutageTime(profileName, e.Message, e.StackTrace);
}
+
+ try
+ {
+ newProfile.ProxyExcludeList = AccessWMI.GetProxyExcludeList(profileName, cancelToken);
+ }
+ catch (Exception e)
+ {
+ DPCServiceEvents.Log.ErrorGettingProxyExclusions(profileName, e.Message, e.StackTrace);
+ }
}
return newProfile;
diff --git a/README.md b/README.md
index 16273a5..94f4ef8 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,13 @@ This script is very much in development so please take care and people to active
# Release Notes
+## Version 5.2.2
+- Updated Proxy examples in ADMX files
+- Fixed incorrect Proxy Exceptions option for User Backup Tunnel in ADMX files
+- Fixed potential null pointer exception when using proxy exclusions
+- Fixed issue where an & in the profile would break profile ingestion
+- Improved IPv4 and IPv6 validation, big thanks to @ryannewington for this PR
+
## Version 5.2.1
- Fixed issue where DPC would not exit correctly
- Updated Profile management logging to show additional information in rare circumstances
diff --git a/ServiceIntergrationTests/UserProfileTests.cs b/ServiceIntergrationTests/UserProfileTests.cs
index 9fd8cd1..0dc53ea 100644
--- a/ServiceIntergrationTests/UserProfileTests.cs
+++ b/ServiceIntergrationTests/UserProfileTests.cs
@@ -81,6 +81,55 @@ public void BasicUserProfile(ProfileType profileType)
HelperFunctions.AssertProfileMatches(profileName, profile.GetProfile(), TestContext);
}
+ [DataTestMethod]
+ [DataRow("")]
+ [DataRow(" ")] //Non-breaking Space
+ [DataRow("\"")]
+ [DataRow("\'")]
+ [DataRow("&")]
+ [DataRow("<")]
+ [DataRow(">")]
+ [DataRow("¢")]
+ [DataRow("£")]
+ [DataRow("¥")]
+ [DataRow("€")]
+ [DataRow("©")]
+ [DataRow("®")]
+ [DataRow("™")]
+ public void BasicUserProfileWithWeirdCharsInRouteName(string character)
+ {
+ string profileName = TestContext.TestName;
+
+ VPNProfileCreator profile = new VPNProfileCreator(ProfileType.User, false);
+ profile.LoadUserProfile(profileName,
+ TunnelType.SplitTunnel,
+ HelperFunctions.DefaultConnectionURL,
+ new List() { "47beabc922eae80e78783462a79f45c254fde68b" },
+ new List() { "27ac9369faf25207bb2627cefaccbe4ef9c319b8" },
+ new List() { "NPS01.Test.local" },
+ routeList: new Dictionary
+ {
+ { "10.0.0.0/8", "Server " + character + " Client Network" }
+ }
+ );
+ profile.Generate();
+ TestContext.WriteLine(profile.GetValidationFailures());
+ TestContext.WriteLine(profile.GetValidationWarnings());
+ Assert.IsFalse(profile.ValidateFailed());
+ Assert.IsFalse(profile.ValidateWarnings());
+
+ sharedData.AddProfileUpdate(profile.GetProfileUpdate());
+
+ sharedData.HandleProfileUpdates();
+ Assert.IsTrue(HelperFunctions.CheckProfileExists(profileName));
+
+ //Assert that all updates have happened
+ Assert.AreEqual(0, sharedData.PendingUpdates());
+
+ //Check update was fully accepted by WMI
+ HelperFunctions.AssertProfileMatches(profileName, profile.GetProfile(), TestContext);
+ }
+
[DataTestMethod]
[DataRow(ProfileType.User)]
[DataRow(ProfileType.UserBackup)]
@@ -1938,7 +1987,7 @@ public void BasicUserProfileWithIPv6Routes(ProfileType profileType)
[DataTestMethod]
[DataRow(ProfileType.User)]
[DataRow(ProfileType.UserBackup)]
- public void BasicUserProfileWithSillyRoutes(ProfileType profileType)
+ public void BasicUserProfileWithDefaultRoutes(ProfileType profileType)
{
string profileName = TestContext.TestName;
@@ -1964,7 +2013,7 @@ public void BasicUserProfileWithSillyRoutes(ProfileType profileType)
TestContext.WriteLine(profile.GetValidationFailures());
TestContext.WriteLine(profile.GetValidationWarnings());
Assert.IsFalse(profile.ValidateFailed());
- Assert.IsTrue(profile.ValidateWarnings());
+ Assert.IsFalse(profile.ValidateWarnings());
sharedData.AddProfileUpdate(profile.GetProfileUpdate());