0

In order to programmatically change my adapter (NetworkInterface) DNS IP addresses in my WPF application, I followed this question : Change DNS in windows using c# answer and add it as follow :

    /// <summary>
    /// Method to set the DNS IP addresses of a given <see cref="NetworkInterface"/>
    /// Note : using "Win32_NetworkAdapterConfiguration" library, so only on Windows OS
    /// </summary>
    /// <param name="ni">The <see cref="NetworkInterface"/> adapter to modify its DNS IP addresses along given <paramref name="addresses"/></param>
    /// <param name="addresses">The IP addresses to store in <paramref name="ni"/> adapter as its new DNS IP addresses</param>
    public static void SetDNS(this NetworkInterface ni, string[] addresses)
    {
        if (ni == null) return;

        ManagementClass objMC = new ManagementClass("Win32_NetworkAdapterConfiguration");
        ManagementObjectCollection objMOC = objMC.GetInstances();
        foreach (ManagementObject objMO in objMOC)
        {
            if ((bool)objMO["IPEnabled"])
            {
                if (objMO["Caption"].ToString().Contains(ni.Description))
                {
                    ManagementBaseObject objdns = objMO.GetMethodParameters("SetDNSServerSearchOrder");
                    if (objdns != null)
                    {
                        objdns["DNSServerSearchOrder"] = addresses;
                        objMO.InvokeMethod("SetDNSServerSearchOrder", objdns, null);
                    }
                }
            }
        }
    }

Entering debug mode show that it correctly call this function, looping through ManagementObjectCollection, ending one of the items to correctly match my 2 if statement, and finally call

objdns["DNSServerSearchOrder"] = addresses;
                            objMO.InvokeMethod("SetDNSServerSearchOrder", objdns, null);

see : https://i.stack.imgur.com/hxqhS.jpg

But my DNS IP addresses aren't changed when I check with an ipconfig /all CLI ! How to effectively change my DNS addresses in windows directly via C#, and not via calling a cmd.exe process behind.

Thank you.

TRex
  • 127
  • 1
  • 8
  • `InvokeMethod()` returns a value. Add: `var outPar = objMO.InvokeMethod("SetDNSServerSearchOrder", objdns, null); uint result = (uint)outPar.GetPropertyValue("ReturnValue");`, see what that is. Compare with the [SetDNSServerSearchOrder()](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/setdnsserversearchorder-method-in-class-win32-networkadapterconfiguration) return values. – Jimi Sep 04 '21 at 12:54
  • BTW, you have 3 disposable objects you really need to dispose, since you're using System.Management. – Jimi Sep 04 '21 at 13:00
  • @Jimi, thank you to point me in the way to further understanding the issue : The result code is 91, so according to your link : "Access Denied" issue. Thank you too for the dispose, i guessed that creating a `new ManagementClass` would create a managed object that GC would take care of, but your comment seems I was wrong. – TRex Sep 04 '21 at 14:05
  • When an object exposes a `Dispose()` method, you need to call it, or declare the object with a `using` statement. Then the GC can do its job. -- BTW, `outPar` is also disposable :) – Jimi Sep 04 '21 at 14:07
  • Now I have another error code : 70 (Invalid IP), what I'm trying to pass here is a `string[4]` of 4 IP addresses for my DNS : the 2 first are IPv6 ones and the 2 last are IPv4 ones. Eg : https://imgur.com/a/Ospnozu I guess this `SetDNS()` only work for IPv4 addresses ? – TRex Sep 04 '21 at 17:18
  • Yes, IPv6 is not supported here, neither reading or writing. Note that it's just adding those values to the `Registry` + `WM_SETTINGSCHANGED`. The reg values are in `HKLM\SYSTEM\CurrentControlSet\services\TCPIP6\Parameters\Interfaces\{Interface GUID}\NameServer`. Of course you can execute `netsh` silently. – Jimi Sep 04 '21 at 19:15

1 Answers1

0

So here is what I ending with to change my system DNS addresses. Thanks to @jimi for helping me in my way to better understanding this.

So after all I'm using netsh silently to set new DNS addresses, as pointed out by @jimi, because IPv6 isn't supported by the original solution I tried (via WMI)

So far (didn't throughfully tested it) it work !

Here is what I did :

    /// <summary>
    /// Start a <see cref="Process"/>
    /// </summary>
    /// <param name="processName">The <see cref="ProcessStartInfo.FileName"/> (usually name of the exe / command to start)</param>
    /// <param name="args">The <see cref="ProcessStartInfo.Arguments"/> (the argument of the command)</param>
    /// <param name="verb">The <see cref="ProcessStartInfo.Verb"/>. Use "runas" to start the process with admin priviledge (default is null)</param>
    /// <param name="useShell">The <see cref="ProcessStartInfo.UseShellExecute"/>. Does the process run silently or not (silent by default)</param>
    /// <param name="redirectErros">The <see cref="ProcessStartInfo.RedirectStandardError"/>. Do we redirect standard error ? (true by default)</param>
    /// <param name="redirectOutput">The <see cref="ProcessStartInfo.RedirectStandardOutput"/>. Do we redirect standard output ? (true by default)</param>
    /// <param name="noWindow">The <see cref="ProcessStartInfo.CreateNoWindow"/>. Do we prevent the creation of CMD window to run silently ? (silent by default)</param>
    /// <returns>True if <paramref name="processName"/> isn't null and process execution succeeded. False if <paramref name="processName"/> is null or empty.
    /// Throw an <see cref="Exception"/> if execution failed</returns>
    public static bool StartProcess(string processName, string args, string verb = null, bool useShell = false, bool redirectErros = true, bool redirectOutput = true, bool noWindow = true)
    {
        if (string.IsNullOrWhiteSpace(processName))
            return false;
        ProcessStartInfo psi = new ProcessStartInfo();
        psi.FileName = processName;
        psi.Arguments = args;
        psi.UseShellExecute = useShell;
        psi.RedirectStandardOutput = redirectOutput;
        psi.RedirectStandardError = redirectErros;
        psi.CreateNoWindow = noWindow;
        if (verb != null)
            psi.Verb = verb;
        Process proc = Process.Start(psi);
        proc.WaitForExit();
        string errors = proc.StandardError.ReadToEnd();
        string output = proc.StandardOutput.ReadToEnd();
        if (proc.ExitCode != 0)
            throw new Exception(processName + " exit code: " + proc.ExitCode.ToString() + " " + (!string.IsNullOrEmpty(errors) ? " " + errors : "") + " " + (!string.IsNullOrEmpty(output) ? " " + output : ""));
        return true;
    }

    /// <summary>
    /// Convinient method to start a "netsh" process as admin to set a new DNS IP address calling <see cref="StartProcess(string, string, string, bool, bool, bool)"/>
    /// </summary>
    /// <param name="interfaceName">The name of the interface to set its new <paramref name="address"/> IP ddress</param>
    /// <param name="address">The new IP address to set of the <paramref name="interfaceName"/> DNS</param>
    /// <param name="isPrimary">Is this new DNS IP address is a primary one ?</param>
    /// <returns><see cref="StartProcess(string, string, string, bool, bool, bool)"/> return value, 
    /// or false if <paramref name="address"/> isn't a correct IP address</returns>
    public static bool netshSetNewDNS(string interfaceName, string address, bool isPrimary)
    {
        var ipVer = IPversion(address, RGXVX);
        if (!(ipVer[0] || ipVer[1]))
            return false;
        return netshSetNewDNS(interfaceName, address, isPrimary, ipVer[0]);
    }

    /// <summary>
    /// Convinient method to start a "netsh" process as admin to set a new DNS IP address calling <see cref="netshSetNewDNS(string, string, bool)"/>
    /// </summary>
    /// <param name="interfaceName">The name of the interface to set its new <paramref name="address"/> IP ddress</param>
    /// <param name="address">The new IP address to set of the <paramref name="interfaceName"/> DNS</param>
    /// <param name="isPrimary">Is this new DNS IP address is a primary one ?</param>
    /// <param name="isIPv6">Does <paramref name="address"/> is IPv6 ?</param>
    /// <returns><see cref="netshSetNewDNS(string, string, bool)"/> return value</returns>
    public static bool netshSetNewDNS(string interfaceName, string address, bool isPrimary, bool isIPv6)
    {            
        string arg = string.Format("interface {0} {1} dnsservers \"{2}\"{3} {4} {5}", isIPv6 ? "ipv6" : "ipv4", isPrimary ? "set" : "add", interfaceName, isPrimary ? " static" : "", address, isPrimary ? "primary" : "index=2");
        return StartProcess("netsh", arg, "runas");
    }

    /// <summary>
    /// Method to set the DNS IP addresses of a given <see cref="NetworkInterface"/>
    /// note : we use netsh silently.
    /// see : https://www.tenforums.com/tutorials/77444-change-ipv4-ipv6-dns-server-address-windows.html
    /// </summary>
    /// <param name="ni">The <see cref="NetworkInterface"/> adapter to modify its DNS IP addresses along given <paramref name="addresses"/></param>
    /// <param name="ipv4">The IPv4 addresses to store in <paramref name="ni"/> adapter as its new DNS IP addresses</param>
    /// <param name="ipv6">The IPv6 addresses to store in <paramref name="ni"/> adapter as its new DNS IP addresses</param>
    public static void SetDNS(this NetworkInterface ni, string[] ipv4, string[] ipv6)
    {
        if (!(ipv4.Any(add => IsIP(add, RGXV4)) || ipv6.Any(add => IsIP(add, RGXV6))))
        {
            Debug.WriteLine("None of the suplied addresses are IPv4/6.\nCan not update DNS IP addresses.");
            return;
        }
        // delete current IPv4 DNS
        StartProcess("netsh", "interface ipv4 delete dnsservers \"" + ni.Name + "\" all", "runas");
        // delete current IPv6 DNS
        StartProcess("netsh", "interface ipv6 delete dnsservers \"" + ni.Name + "\" all", "runas");
        string address;
        //set new IPv4 DNS addresses
        if (ipv4 != null && ipv4.Length>0)
        {                
            //primary
            address = ipv4[0];                
            try
            { bool res = netshSetNewDNS(ni.Name, address, true, false); }
            catch(Exception e)
            { Debug.WriteLine(e.Message); }
            if (ipv4.Length>1)
            {
                //secondary
                address = ipv4[1];
                try
                { bool res = netshSetNewDNS(ni.Name, address, false, false); }
                catch (Exception e)
                { Debug.WriteLine(e.Message); }
            }
        }
        
        //set new IPv6 DNS addresses
        if (ipv6 != null && ipv6.Length > 0)
        {
            
            //primary
            address = ipv6[0];
            try
            { bool res = netshSetNewDNS(ni.Name, address, true, true); }
            catch (Exception e)
            { Debug.WriteLine(e.Message); }

            if (ipv6.Length > 1)
            {
                //secondary
                address = ipv6[1];
                try
                { bool res = netshSetNewDNS(ni.Name, address, false, true); }
                catch (Exception e)
                { Debug.WriteLine(e.Message); }
            }
        }
    }

I have though 1 "minor" issue : setting ipv6 (+ipv4 ?) addresses suffer performance issue.

Whereas with only new (2) ipv4 IP addresses it is almost immediate (1 second max), with 2 ipv6 + 2 ipv4 setting, it take up to 10 seconds before actually (correctly) changing my DNS IP addresses.

Debugging show that it freeze on proc.WaitForExit(); of StartProcess(...) function called on the secondary IPv4 DNS address setting (in SetDNS(...) bool res = netshSetNewDNS(ni.Name, address, false, false);)!

It is strange it always freeze here because when I call with only 2 new IPv4 DNS addresses setting it doesn't freeze this time ...

TRex
  • 127
  • 1
  • 8