osdns 0.1.3

Safe, transactional control of operating-system DNS configuration
Documentation
# Run only on a disposable CI VM, as administrator. Never select its uplink.
$ErrorActionPreference = 'Stop'
$adapterName = 'osdns-ci-loopback'
if (Get-NetAdapter -Name $adapterName -ErrorAction SilentlyContinue) {
    throw "Refusing to reuse existing adapter $adapterName"
}

Add-Type -TypeDefinition @'
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
public static class OsdnsLoopback {
    [StructLayout(LayoutKind.Sequential)]
    struct DeviceInfo { public uint Size; public Guid ClassGuid; public uint DevInst; public UIntPtr Reserved; }
    [DllImport("setupapi.dll", SetLastError=true)]
    static extern IntPtr SetupDiCreateDeviceInfoList(ref Guid cls, IntPtr parent);
    [DllImport("setupapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    static extern bool SetupDiCreateDeviceInfo(IntPtr set, string name, ref Guid cls, string description, IntPtr parent, uint flags, ref DeviceInfo info);
    [DllImport("setupapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    static extern bool SetupDiSetDeviceRegistryProperty(IntPtr set, ref DeviceInfo info, uint property, byte[] buffer, uint size);
    [DllImport("setupapi.dll", SetLastError=true)]
    static extern bool SetupDiCallClassInstaller(uint function, IntPtr set, ref DeviceInfo info);
    [DllImport("setupapi.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    static extern bool SetupDiGetDeviceInstanceId(IntPtr set, ref DeviceInfo info, StringBuilder id, uint size, out uint required);
    [DllImport("setupapi.dll")]
    static extern bool SetupDiDestroyDeviceInfoList(IntPtr set);
    [DllImport("newdev.dll", CharSet=CharSet.Unicode, SetLastError=true)]
    static extern bool UpdateDriverForPlugAndPlayDevices(IntPtr parent, string hardware, string inf, uint flags, out bool reboot);
    static void Check(bool ok) { if (!ok) throw new Win32Exception(Marshal.GetLastWin32Error()); }
    public static string Create(string inf) {
        Guid cls = new Guid("4d36e972-e325-11ce-bfc1-08002be10318");
        IntPtr set = SetupDiCreateDeviceInfoList(ref cls, IntPtr.Zero);
        if (set == new IntPtr(-1)) throw new Win32Exception(Marshal.GetLastWin32Error());
        DeviceInfo info = new DeviceInfo { Size = (uint)Marshal.SizeOf(typeof(DeviceInfo)) };
        bool registered = false;
        try {
            Check(SetupDiCreateDeviceInfo(set, "Net", ref cls, "osdns CI loopback", IntPtr.Zero, 1, ref info));
            byte[] hardware = Encoding.Unicode.GetBytes("*MSLOOP\0\0");
            Check(SetupDiSetDeviceRegistryProperty(set, ref info, 1, hardware, (uint)hardware.Length));
            Check(SetupDiCallClassInstaller(0x19, set, ref info)); // DIF_REGISTERDEVICE
            registered = true;
            bool reboot;
            Check(UpdateDriverForPlugAndPlayDevices(IntPtr.Zero, "*MSLOOP", inf, 5, out reboot));
            if (reboot) throw new Exception("Loopback installation requires a reboot");
            var id = new StringBuilder(512);
            uint required;
            Check(SetupDiGetDeviceInstanceId(set, ref info, id, 512, out required));
            return id.ToString();
        } catch {
            if (registered) SetupDiCallClassInstaller(5, set, ref info); // DIF_REMOVE
            throw;
        } finally { SetupDiDestroyDeviceInfoList(set); }
    }
}
'@

$deviceId = [OsdnsLoopback]::Create("$env:windir\INF\netloop.inf")
try {
    $deadline = (Get-Date).AddSeconds(30)
    do {
        $adapter = Get-CimInstance Win32_NetworkAdapter | Where-Object PNPDeviceID -EQ $deviceId
        if ($adapter.NetConnectionID) { break }
        Start-Sleep -Milliseconds 250
    } while ((Get-Date) -lt $deadline)
    if (!$adapter.NetConnectionID) { throw "Loopback adapter did not appear: $deviceId" }
    Rename-NetAdapter -Name $adapter.NetConnectionID -NewName $adapterName
    Set-NetIPInterface -InterfaceAlias $adapterName -AddressFamily IPv4 -Dhcp Disabled
    New-NetIPAddress -InterfaceAlias $adapterName -IPAddress 192.0.2.1 -PrefixLength 24 | Out-Null
    Set-DnsClientServerAddress -InterfaceAlias $adapterName -ResetServerAddresses
    $env:OSDNS_TEST_INTERFACE = $adapterName
    $env:OSDNS_ALLOW_SYSTEM_MUTATION = '1'
    cargo +1.98.0 test --features test-util --test backend_matrix --test windows -- --nocapture --test-threads=1
    if ($LASTEXITCODE -ne 0) { throw "Windows integration tests failed ($LASTEXITCODE)" }
} finally {
    pnputil /remove-device $deviceId
    if ($LASTEXITCODE -ne 0) { throw "Failed to remove disposable adapter $deviceId" }
    Remove-Item Env:OSDNS_TEST_INTERFACE -ErrorAction SilentlyContinue
    Remove-Item Env:OSDNS_ALLOW_SYSTEM_MUTATION -ErrorAction SilentlyContinue
}