Repository: ansible/ansible
Pull request: Added win_http_proxy and win_inet_proxy
##### SUMMARY
Adds 2 modules that can be used to configure proxy settings on Windows;

* win_http_proxy - Configures proxy settings for WinHTTP
* win_inet_proxy - Configures proxy settings for WinINet (IE)

While the input options are similar, WinINet offers a few extra options and WinHTTP can also import from WinINet. This made it difficult to have just 1 `win_proxy` module and so I created a module for each for a cleaner UI. Hopefully the documentation makes this clear as to that they do and link to each other.

Fixes https://github.com/ansible/ansible/issues/22663

##### ISSUE TYPE
- Feature Pull Request

##### COMPONENT NAME
win_http_proxy
win_inet_proxy

Problem statement:
Module proposal: win_wininet (configure IE proxy settings)
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
win_wininet (proposed name)

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
2.2.1.0
```

##### SUMMARY
It hard/non-obvious to configure the ansible_user's "Internet Options" proxy configuration on Windows. This proposal suggests adding a new module to do that.

The potential benefit is that various other downloaders could then be made to work *implicitly* using wininet.dll. For example, .Net's System.Net.WebClient, or the "win_chocolatey" module.

##### OTHER NOTES

There are a number of **wrong** proposals online for configuring the IE proxy settings:
* Just set some specific registry keys (only works if the user account has had its IE profile initialized previously by running IE *at least once* -- which isn't helpful at all for automation)
* Set some registry keys and then "poke" wininet.dll to force a reload (same issue as last point)
* Use "netsh winhttp set proxy ..." (fundamentally misunderstands the difference between wininet and winhttp, which are different libraries used by different things in Windows)

A better solution is to initialize IE via wininet.dll itself. This can be done in PowerShell through inline C# with data marshaling for options content. Such a solution should be fairly easy for Ansible to deliver/manage.

I have a proof-of-concept for this as a binary written in C, which I have been using so far by first uploading the binary, executing it, and then cleaning it up, in multiple, separate Ansible tasks. (I can post the relevant source as soon as I get it cleared by my employer.)

Hints from issue discussion:
A module for this is acceptable. However win_wininet really looks weird. I wonder if another (more descriptive) name makes more sense, like **win_internet_options** or **win_proxy**.

I would say, go for it !
@dagwieers good point RE name, though **win_proxy** might too ambiguous (wininet vs winhttp); **win_internet_options** seems good though, because of its similarity to the control panel name for wininet. Would also make it more generalizable for the future (eg to add some non-proxy options in the future).
PoC code now available at https://gist.github.com/johnboy2/a862060b386d354f488ca2c6049e9105. 

Note that I am merely "putting this out there" in case it is useful to others, or if somebody wants to take on modularizing this for Ansible. (I won't likely have time to attempt doing that myself any time soon -- certainly not within the next few months; which is why I'm making this available.)
I'm all for having a module for this and I can't claim to know the specifics but I'm unsure as to whether we need to use some PInvoke to the `wininet.dll` as I've gotten this to work on Server 2016. If I run the following task;

```
- name: Set the default profile proxy settings
  win_regedit:
    path: HKLM:\ANSIBLE\Software\Microsoft\Windows\CurrentVersion\Internet Settings
    name: '{{ item.name }}'
    data: '{{ item.data }}'
    type: '{{ item.type }}'
    state: present
    hive: C:\Users\Default\NTUSER.dat
  with_items:
  - name: ProxyEnable
    data: 1
    type: dword
  - name: ProxyServer
    data: proxy.example.org:8080
    type: string
```

Every new user profile that is created on that host will have the proxy host enabled and set to `proxy.example.org:8080`. Any existing user would either have to delete their profile and log in again, or manually configure the settings for their account.
In my experience, registry-based proxy changes only work **after** wininet has gone through first-time initialization (ie by opening up IE or at least the IE control panel _at least once_). If you don't do that, those changes are meaningless and ignored.

The approach referenced by my gist above covers the case where no such "first run" has occurred.

Commit message: Added win_http_proxy and win_inet_proxy

Patch to review:
diff --git a/lib/ansible/modules/windows/win_http_proxy.ps1 b/lib/ansible/modules/windows/win_http_proxy.ps1
new file mode 100644
index 00000000000000..750bb1473dd002
--- /dev/null
+++ b/lib/ansible/modules/windows/win_http_proxy.ps1
@@ -0,0 +1,264 @@
+#!powershell
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.AddType
+
+$spec = @{
+    options = @{
+        bypass = @{ type = "list" }
+        proxy = @{ type = "raw" }
+        source = @{ type = "str"; choices = @("ie") }
+    }
+    mutually_exclusive = @(
+        @("proxy", "source"),
+        @("bypass", "source")
+    )
+    required_by = @{
+        bypass = @("proxy")
+    }
+    supports_check_mode = $true
+}
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
+
+$proxy = $module.Params.proxy
+$bypass = $module.Params.bypass
+$source = $module.Params.source
+
+# Parse the raw value, it should be a Dictionary or String
+if ($proxy -is [System.Collections.IDictionary]) {
+    $valid_keys = [System.Collections.Generic.List`1[String]]@("http", "https", "ftp", "socks")
+    # Check to make sure we don't have any invalid keys in the dict
+    $invalid_keys = [System.Collections.Generic.List`1[String]]@()
+    foreach ($k in $proxy.Keys) {
+        if ($k -notin $valid_keys) {
+            $invalid_keys.Add($k)
+        }
+    }
+
+    if ($invalid_keys.Count -gt 0) {
+        $invalid_keys = $invalid_keys | Sort-Object  # So our test assertion doesn't fail due to random ordering
+        $module.FailJson("Invalid keys found in proxy: $($invalid_keys -join ', '). Valid keys are $($valid_keys -join ', ').")
+    }
+
+    # Build the proxy string in the form 'protocol=host;', the order of valid_keys is also important
+    $proxy_list = [System.Collections.Generic.List`1[String]]@()
+    foreach ($k in $valid_keys) {
+        if ($proxy.ContainsKey($k)) {
+            $proxy_list.Add("$k=$($proxy.$k)")
+        }
+    }
+    $proxy = $proxy_list -join ";"
+} elseif ($null -ne $proxy) {
+    $proxy = $proxy.ToString()
+}
+
+if ($bypass) {
+    $bypass = $bypass -join ';'
+}
+
+$win_http_invoke = @'
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ansible.WinHttpProxy
+{
+    internal class NativeHelpers
+    {
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class WINHTTP_CURRENT_USER_IE_PROXY_CONFIG : IDisposable
+        {
+            public bool fAutoDetect;
+            public IntPtr lpszAutoConfigUrl;
+            public IntPtr lpszProxy;
+            public IntPtr lpszProxyBypass;
+
+            public void Dispose()
+            {
+                if (lpszAutoConfigUrl != IntPtr.Zero)
+                    Marshal.FreeHGlobal(lpszAutoConfigUrl);
+                if (lpszProxy != IntPtr.Zero)
+                    Marshal.FreeHGlobal(lpszProxy);
+                if (lpszProxyBypass != IntPtr.Zero)
+                    Marshal.FreeHGlobal(lpszProxyBypass);
+                GC.SuppressFinalize(this);
+            }
+            ~WINHTTP_CURRENT_USER_IE_PROXY_CONFIG() { this.Dispose(); }
+        }
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class WINHTTP_PROXY_INFO : IDisposable
+        {
+            public UInt32 dwAccessType;
+            public IntPtr lpszProxy;
+            public IntPtr lpszProxyBypass;
+
+            public void Dispose()
+            {
+                if (lpszProxy != IntPtr.Zero)
+                    Marshal.FreeHGlobal(lpszProxy);
+                if (lpszProxyBypass != IntPtr.Zero)
+                    Marshal.FreeHGlobal(lpszProxyBypass);
+                GC.SuppressFinalize(this);
+            }
+            ~WINHTTP_PROXY_INFO() { this.Dispose(); }
+        }
+    }
+
+    internal class NativeMethods
+    {
+        [DllImport("Winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        public static extern bool WinHttpGetDefaultProxyConfiguration(
+            [Out] NativeHelpers.WINHTTP_PROXY_INFO pProxyInfo);
+
+        [DllImport("Winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        public static extern bool WinHttpGetIEProxyConfigForCurrentUser(
+            [Out] NativeHelpers.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG pProxyConfig);
+
+        [DllImport("Winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        public static extern bool WinHttpSetDefaultProxyConfiguration(
+            NativeHelpers.WINHTTP_PROXY_INFO pProxyInfo);
+    }
+
+    public class Win32Exception : System.ComponentModel.Win32Exception
+    {
+        private string _msg;
+
+        public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
+        public Win32Exception(int errorCode, string message) : base(errorCode)
+        {
+            _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
+        }
+
+        public override string Message { get { return _msg; } }
+        public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
+    }
+
+    public class WinINetProxy
+    {
+        public bool AutoDetect;
+        public string AutoConfigUrl;
+        public string Proxy;
+        public string ProxyBypass;
+    }
+
+    public class WinHttpProxy
+    {
+        public string Proxy;
+        public string ProxyBypass;
+
+        public WinHttpProxy()
+        {
+            Refresh();
+        }
+
+        public void Set()
+        {
+            using (NativeHelpers.WINHTTP_PROXY_INFO proxyInfo = new NativeHelpers.WINHTTP_PROXY_INFO())
+            {
+                if (String.IsNullOrEmpty(Proxy))
+                    proxyInfo.dwAccessType = 1; // WINHTTP_ACCESS_TYPE_NO_PROXY
+                else
+                {
+                    proxyInfo.dwAccessType = 3; // WINHTTP_ACCESS_TYPE_NAMED_PROXY
+                    proxyInfo.lpszProxy = Marshal.StringToHGlobalUni(Proxy);
+
+                    if (!String.IsNullOrEmpty(ProxyBypass))
+                        proxyInfo.lpszProxyBypass = Marshal.StringToHGlobalUni(ProxyBypass);
+                }
+
+                if (!NativeMethods.WinHttpSetDefaultProxyConfiguration(proxyInfo))
+                    throw new Win32Exception("WinHttpSetDefaultProxyConfiguration() failed");
+            }
+        }
+
+        public void Refresh()
+        {
+            using (NativeHelpers.WINHTTP_PROXY_INFO proxyInfo = new NativeHelpers.WINHTTP_PROXY_INFO())
+            {
+                if (!NativeMethods.WinHttpGetDefaultProxyConfiguration(proxyInfo))
+                    throw new Win32Exception("WinHttpGetDefaultProxyConfiguration() failed");
+
+                Proxy = Marshal.PtrToStringUni(proxyInfo.lpszProxy);
+                ProxyBypass = Marshal.PtrToStringUni(proxyInfo.lpszProxyBypass);
+            }
+        }
+
+        public static WinINetProxy GetIEProxyConfig()
+        {
+            using (NativeHelpers.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxy = new NativeHelpers.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG())
+            {
+                if (!NativeMethods.WinHttpGetIEProxyConfigForCurrentUser(ieProxy))
+                    throw new Win32Exception("WinHttpGetIEProxyConfigForCurrentUser() failed");
+
+                return new WinINetProxy
+                {
+                    AutoDetect = ieProxy.fAutoDetect,
+                    AutoConfigUrl = Marshal.PtrToStringUni(ieProxy.lpszAutoConfigUrl),
+                    Proxy = Marshal.PtrToStringUni(ieProxy.lpszProxy),
+                    ProxyBypass = Marshal.PtrToStringUni(ieProxy.lpszProxyBypass),
+                };
+            }
+        }
+    }
+}
+'@
+Add-CSharpType -References $win_http_invoke -AnsibleModule $module
+
+$actual_proxy = New-Object -TypeName Ansible.WinHttpProxy.WinHttpProxy
+
+$module.Diff.before = @{
+    proxy = $actual_proxy.Proxy
+    bypass = $actual_proxy.ProxyBypass
+}
+
+if ($source -eq "ie") {
+    # If source=ie we need to get the server and bypass values from the IE configuration
+    $ie_proxy = [Ansible.WinHttpProxy.WinHttpProxy]::GetIEProxyConfig()
+    $proxy = $ie_proxy.Proxy
+    $bypass = $ie_proxy.ProxyBypass
+}
+
+$previous_proxy = $actual_proxy.Proxy
+$previous_bypass = $actual_proxy.ProxyBypass
+
+# Make sure an empty string is converted to $null for easier comparisons
+if ([String]::IsNullOrEmpty($proxy)) {
+    $proxy = $null
+}
+if ([String]::IsNullOrEmpty($bypass)) {
+    $bypass = $null
+}
+
+if ($previous_proxy -ne $proxy -or $previous_bypass -ne $bypass) {
+    $actual_proxy.Proxy = $proxy
+    $actual_proxy.ProxyBypass = $bypass
+
+    if (-not $module.CheckMode) {
+        $actual_proxy.Set()
+
+        # Validate that the change was made correctly and revert if it wasn't. The Set() method won't fail on invalid
+        # values so we need to check again to make sure all was good.
+        $actual_proxy.Refresh()
+        if ($actual_proxy.Proxy -ne $proxy -or $actual_proxy.ProxyBypass -ne $bypass) {
+            $actual_proxy.Proxy = $previous_proxy
+            $actual_proxy.ProxyBypass = $previous_bypass
+            $actual_proxy.Set()
+
+            $module.FailJson("Unknown error when trying to set proxy '$proxy' or bypass '$bypass'")
+        }
+    }
+
+    $module.Result.changed = $true
+}
+
+$module.Diff.after = @{
+    proxy = $proxy
+    bypass = $bypass
+}
+
+$module.ExitJson()
+
diff --git a/lib/ansible/modules/windows/win_http_proxy.py b/lib/ansible/modules/windows/win_http_proxy.py
new file mode 100644
index 00000000000000..c8399421819fdd
--- /dev/null
+++ b/lib/ansible/modules/windows/win_http_proxy.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: win_http_proxy
+version_added: '2.8'
+short_description: Manages proxy settings for WinHTTP
+description:
+- Used to set, remote, or import proxy settings for Windows HTTP Services
+  C(WinHTTP).
+- WinHTTP is a framework used by applications or services, typically .NET
+  applications or non-interactive services, to make web requests.
+options:
+  bypass:
+    description:
+    - A list of hosts that will bypass the set proxy when being accessed.
+    - Use C(<local>) to match hostnames that are not fully qualified domain
+      names. This is useful when needing to connect to intranet sites using
+      just the hostname.
+    - Use C(<-loopback>) to stop automatically bypassing the proxy when
+      connecting through any loopback address like C(127.0.0.1), C(localhost),
+      or the local hostname.
+    - Omit, set to null or an empty string/list to remove the bypass list.
+    - If this is set then I(proxy) must also be set.
+    type: list
+  proxy:
+    description:
+    - A string or dict that specifies the proxy to be set.
+    - If setting a string, should be in the form C(hostname), C(hostname:port),
+      or C(protocol=hostname:port).
+    - If setting a dict, the keys relate are the protocol and the value is the
+      hostname and/or port for the protocol specified.
+    - Valid protocols are C(http), C(https), C(ftp), and C(socks).
+    - Omit, set to null or an empty string to remove the proxy settings.
+  source:
+    description:
+    - Instead of manually specifying the I(proxy) and/or I(bypass), set this to
+      import the proxy from a set source like Internet Explorer.
+    - Using C(ie) will import the Internet Explorer proxy settings for the
+      current active network connection of the current user.
+    - This is like running C(netsh winhttp import proxy source=ie).
+    - The value is imported when the module runs and will not automatically
+      be updated if IE changes the values in the future. The module will have
+      to be run again to sync the latest changes.
+    choices:
+    - ie
+    type: str
+notes:
+- This is not the same as the proxy settings set in Internet Explorer, also
+  known as C(WinINet), use the M(win_inet_proxy) module to manage that instead.
+- These settings are set system wide and not per user, it will require
+  Administrative privileges to run.
+seealso:
+- module: win_inet_proxy
+author:
+- Jordan Borean (@jborean93)
+'''
+
+EXAMPLES = r'''
+- name: Set a proxy to use for all protocols
+  win_http_proxy:
+    proxy: hostname
+
+- name: Set a proxy with a specific port with a bypass list
+  win_http_proxy:
+    proxy: hostname:8080
+    bypass:
+    - server1
+    - server2
+    - <local>
+
+- name: Set the proxy based on the IE proxy settings
+  win_http_proxy:
+    source: ie
+
+- name: Set a proxy for specific protocols
+  win_http_proxy:
+    proxy:
+      http: hostname:8080
+      https: hostname:8443
+
+- name: Set a proxy for specific protocols using a string
+  win_http_proxy:
+    proxy: http=hostname:8080;https=hostname:8443
+    bypass: server1,server2,<-loopback>
+
+- name: Remove any proxy settings
+  win_http_proxy:
+    proxy: ''
+    bypass: ''
+'''
+
+RETURN = r'''
+#
+'''
diff --git a/lib/ansible/modules/windows/win_inet_proxy.ps1 b/lib/ansible/modules/windows/win_inet_proxy.ps1
new file mode 100644
index 00000000000000..da41e33f400f11
--- /dev/null
+++ b/lib/ansible/modules/windows/win_inet_proxy.ps1
@@ -0,0 +1,492 @@
+#!powershell
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.AddType
+
+$spec = @{
+    options = @{
+        auto_detect = @{ type = "bool"; default = $true }
+        auto_config_url = @{ type = "str" }
+        proxy = @{ type = "raw" }
+        bypass = @{ type = "list" }
+        connection = @{ type = "str" }
+    }
+    required_by = @{
+        bypass = @("proxy")
+    }
+    supports_check_mode = $true
+}
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
+
+$auto_detect = $module.Params.auto_detect
+$auto_config_url = $module.Params.auto_config_url
+$proxy = $module.Params.proxy
+$bypass = $module.Params.bypass
+$connection = $module.Params.connection
+
+# Parse the raw value, it should be a Dictionary or String
+if ($proxy -is [System.Collections.IDictionary]) {
+    $valid_keys = [System.Collections.Generic.List`1[String]]@("http", "https", "ftp", "socks")
+    # Check to make sure we don't have any invalid keys in the dict
+    $invalid_keys = [System.Collections.Generic.List`1[String]]@()
+    foreach ($k in $proxy.Keys) {
+        if ($k -notin $valid_keys) {
+            $invalid_keys.Add($k)
+        }
+    }
+
+    if ($invalid_keys.Count -gt 0) {
+        $invalid_keys = $invalid_keys | Sort-Object  # So our test assertion doesn't fail due to random ordering
+        $module.FailJson("Invalid keys found in proxy: $($invalid_keys -join ', '). Valid keys are $($valid_keys -join ', ').")
+    }
+
+    # Build the proxy string in the form 'protocol=host;', the order of valid_keys is also important
+    $proxy_list = [System.Collections.Generic.List`1[String]]@()
+    foreach ($k in $valid_keys) {
+        if ($proxy.ContainsKey($k)) {
+            $proxy_list.Add("$k=$($proxy.$k)")
+        }
+    }
+    $proxy = $proxy_list -join ";"
+} elseif ($null -ne $proxy) {
+    $proxy = $proxy.ToString()
+}
+
+if ($bypass) {
+    $bypass = $bypass -join ';'
+}
+
+$win_inet_invoke = @'
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Collections.Generic;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+
+namespace Ansible.WinINetProxy
+{
+    internal class NativeHelpers
+    {
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class INTERNET_PER_CONN_OPTION_LISTW : IDisposable
+        {
+            public UInt32 dwSize;
+            public IntPtr pszConnection;
+            public UInt32 dwOptionCount;
+            public UInt32 dwOptionError;
+            public IntPtr pOptions;
+
+            public INTERNET_PER_CONN_OPTION_LISTW()
+            {
+                dwSize = (UInt32)Marshal.SizeOf(this);
+            }
+
+            public void Dispose()
+            {
+                if (pszConnection != IntPtr.Zero)
+                    Marshal.FreeHGlobal(pszConnection);
+                if (pOptions != IntPtr.Zero)
+                    Marshal.FreeHGlobal(pOptions);
+                GC.SuppressFinalize(this);
+            }
+            ~INTERNET_PER_CONN_OPTION_LISTW() { this.Dispose(); }
+        }
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class INTERNET_PER_CONN_OPTIONW : IDisposable
+        {
+            public INTERNET_PER_CONN_OPTION dwOption;
+            public ValueUnion Value;
+
+            [StructLayout(LayoutKind.Explicit)]
+            public class ValueUnion
+            {
+                [FieldOffset(0)]
+                public UInt32 dwValue;
+
+                [FieldOffset(0)]
+                public IntPtr pszValue;
+
+                [FieldOffset(0)]
+                public System.Runtime.InteropServices.ComTypes.FILETIME ftValue;
+            }
+
+            public void Dispose()
+            {
+                // We can't just check if Value.pszValue is not IntPtr.Zero as the union means it could be set even
+                // when the value is a UInt32 or FILETIME. We check against a known string option type and only free
+                // the value in those cases.
+                List<INTERNET_PER_CONN_OPTION> stringOptions = new List<INTERNET_PER_CONN_OPTION>
+                {
+                    { INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL },
+                    { INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS },
+                    { INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER }
+                };
+                if (Value != null && Value.pszValue != IntPtr.Zero && stringOptions.Contains(dwOption))
+                    Marshal.FreeHGlobal(Value.pszValue);
+                GC.SuppressFinalize(this);
+            }
+            ~INTERNET_PER_CONN_OPTIONW() { this.Dispose(); }
+        }
+
+        public enum INTERNET_OPTION : uint
+        {
+            INTERNET_OPTION_PER_CONNECTION_OPTION = 75,
+            INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95,
+        }
+
+        public enum INTERNET_PER_CONN_OPTION : uint
+        {
+            INTERNET_PER_CONN_FLAGS = 1,
+            INTERNET_PER_CONN_PROXY_SERVER = 2,
+            INTERNET_PER_CONN_PROXY_BYPASS = 3,
+            INTERNET_PER_CONN_AUTOCONFIG_URL = 4,
+            INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5,
+            INTERNET_PER_CONN_FLAGS_UI = 10,  // IE8+ - Included with Windows 7 and Server 2008 R2
+        }
+
+        [Flags]
+        public enum PER_CONN_FLAGS : uint
+        {
+            PROXY_TYPE_DIRECT = 0x00000001,
+            PROXY_TYPE_PROXY = 0x00000002,
+            PROXY_TYPE_AUTO_PROXY_URL = 0x00000004,
+            PROXY_TYPE_AUTO_DETECT = 0x00000008,
+        }
+    }
+
+    internal class NativeMethods
+    {
+        [DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        public static extern bool InternetQueryOptionW(
+            IntPtr hInternet,
+            NativeHelpers.INTERNET_OPTION dwOption,
+            SafeMemoryBuffer lpBuffer,
+            ref UInt32 lpdwBufferLength);
+
+        [DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        public static extern bool InternetSetOptionW(
+            IntPtr hInternet,
+            NativeHelpers.INTERNET_OPTION dwOption,
+            SafeMemoryBuffer lpBuffer,
+            UInt32 dwBufferLength);
+
+        [DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
+        public static extern UInt32 RasValidateEntryNameW(
+            string lpszPhonebook,
+            string lpszEntry);
+    }
+
+    internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        public SafeMemoryBuffer() : base(true) { }
+        public SafeMemoryBuffer(int cb) : base(true)
+        {
+            base.SetHandle(Marshal.AllocHGlobal(cb));
+        }
+        public SafeMemoryBuffer(IntPtr handle) : base(true)
+        {
+            base.SetHandle(handle);
+        }
+
+        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+        protected override bool ReleaseHandle()
+        {
+            Marshal.FreeHGlobal(handle);
+            return true;
+        }
+    }
+
+    public class Win32Exception : System.ComponentModel.Win32Exception
+    {
+        private string _msg;
+
+        public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
+        public Win32Exception(int errorCode, string message) : base(errorCode)
+        {
+            _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
+        }
+
+        public override string Message { get { return _msg; } }
+        public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
+    }
+
+    public class WinINetProxy
+    {
+        private string Connection;
+
+        public string AutoConfigUrl;
+        public bool AutoDetect;
+        public string Proxy;
+        public string ProxyBypass;
+
+        public WinINetProxy(string connection)
+        {
+            Connection = connection;
+            Refresh();
+        }
+
+        public static bool IsValidConnection(string name)
+        {
+            // RasValidateEntryName is used to verify is a name can be a valid phonebook entry. It returns 0 if no
+            // entry exists and 183 if it already exists. We just need to check if it returns 183 to verify the
+            // connection name.
+            return NativeMethods.RasValidateEntryNameW(null, name) == 183;
+        }
+
+        public void Refresh()
+        {
+            using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
+            using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
+            using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
+            using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
+            {
+                NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options = new NativeHelpers.INTERNET_PER_CONN_OPTIONW[]
+                {
+                    connFlags, autoConfigUrl, server, bypass
+                };
+
+                try
+                {
+                    QueryOption(options, Connection);
+                }
+                catch (Win32Exception e)
+                {
+                    if (e.NativeErrorCode == 87) // ERROR_INVALID_PARAMETER
+                    {
+                        // INTERNET_PER_CONN_FLAGS_UI only works for IE8+, try the fallback in case we are still working
+                        // with an ancient version.
+                        connFlags.dwOption = NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS;
+                        QueryOption(options, Connection);
+                    }
+                    else
+                        throw;
+                }
+
+                NativeHelpers.PER_CONN_FLAGS flags = (NativeHelpers.PER_CONN_FLAGS)connFlags.Value.dwValue;
+
+                AutoConfigUrl = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL)
+                    ? Marshal.PtrToStringUni(autoConfigUrl.Value.pszValue) : null;
+                AutoDetect = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT);
+                if (flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY))
+                {
+                    Proxy = Marshal.PtrToStringUni(server.Value.pszValue);
+                    ProxyBypass = Marshal.PtrToStringUni(bypass.Value.pszValue);
+                }
+                else
+                {
+                    Proxy = null;
+                    ProxyBypass = null;
+                }
+            }
+        }
+
+        public void Set()
+        {
+            using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
+            using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
+            using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
+            using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
+            {
+                List<NativeHelpers.INTERNET_PER_CONN_OPTIONW> options = new List<NativeHelpers.INTERNET_PER_CONN_OPTIONW>();
+
+                // PROXY_TYPE_DIRECT seems to always be set, need to verify
+                NativeHelpers.PER_CONN_FLAGS flags = NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_DIRECT;
+                if (AutoDetect)
+                    flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT;
+
+                if (!String.IsNullOrEmpty(AutoConfigUrl))
+                {
+                    flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL;
+                    autoConfigUrl.Value.pszValue = Marshal.StringToHGlobalUni(AutoConfigUrl);
+                }
+                options.Add(autoConfigUrl);
+
+                if (!String.IsNullOrEmpty(Proxy))
+                {
+                    flags |= NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY;
+                    server.Value.pszValue = Marshal.StringToHGlobalUni(Proxy);
+                }
+                options.Add(server);
+
+                if (!String.IsNullOrEmpty(ProxyBypass))
+                    bypass.Value.pszValue = Marshal.StringToHGlobalUni(ProxyBypass);
+                options.Add(bypass);
+
+                connFlags.Value.dwValue = (UInt32)flags;
+                options.Add(connFlags);
+
+                SetOption(options.ToArray(), Connection);
+
+                // Tell IE that the proxy settings have been changed.
+                if (!NativeMethods.InternetSetOptionW(
+                    IntPtr.Zero,
+                    NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PROXY_SETTINGS_CHANGED,
+                    new SafeMemoryBuffer(IntPtr.Zero),
+                    0))
+                {
+                    throw new Win32Exception("InternetSetOptionW(INTERNET_OPTION_PROXY_SETTINGS_CHANGED) failed");
+                }
+            }
+        }
+
+        internal static NativeHelpers.INTERNET_PER_CONN_OPTIONW CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION option)
+        {
+            return new NativeHelpers.INTERNET_PER_CONN_OPTIONW
+            {
+                dwOption = option,
+                Value = new NativeHelpers.INTERNET_PER_CONN_OPTIONW.ValueUnion(),
+            };
+        }
+
+        internal static void QueryOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
+        {
+            using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
+            using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
+            {
+                UInt32 bufferSize = optionList.dwSize;
+                if (!NativeMethods.InternetQueryOptionW(
+                    IntPtr.Zero,
+                    NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
+                    optionListPtr,
+                    ref bufferSize))
+                {
+                    throw new Win32Exception("InternetQueryOptionW(INTERNET_OPTION_PER_CONNECTION_OPTION) failed");
+                }
+
+                for (int i = 0; i < options.Length; i++)
+                {
+                    IntPtr opt = IntPtr.Add(optionList.pOptions, i * Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW)));
+                    NativeHelpers.INTERNET_PER_CONN_OPTIONW option = (NativeHelpers.INTERNET_PER_CONN_OPTIONW)Marshal.PtrToStructure(opt,
+                        typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
+                    options[i].Value = option.Value;
+                    option.Value = null;  // Stops the GC from freeing the same memory twice
+                }
+            }
+        }
+
+        internal static void SetOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
+        {
+            using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
+            using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
+            {
+                if (!NativeMethods.InternetSetOptionW(
+                    IntPtr.Zero,
+                    NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
+                    optionListPtr,
+                    optionList.dwSize))
+                {
+                    throw new Win32Exception("InternetSetOptionW(INTERNET_OPTION_PER_CONNECTION_OPTION) failed");
+                }
+            }
+        }
+
+        internal static SafeMemoryBuffer MarshalOptionList(NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList,
+            NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection)
+        {
+            optionList.pszConnection = Marshal.StringToHGlobalUni(connection);
+            optionList.dwOptionCount = (UInt32)options.Length;
+
+            int optionSize = Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
+            optionList.pOptions = Marshal.AllocHGlobal(optionSize * options.Length);
+            for (int i = 0; i < options.Length; i++)
+            {
+                IntPtr option = IntPtr.Add(optionList.pOptions, i * optionSize);
+                Marshal.StructureToPtr(options[i], option, false);
+            }
+
+            SafeMemoryBuffer optionListPtr = new SafeMemoryBuffer((int)optionList.dwSize);
+            Marshal.StructureToPtr(optionList, optionListPtr.DangerousGetHandle(), false);
+            return optionListPtr;
+        }
+    }
+}
+'@
+Add-CSharpType -References $win_inet_invoke -AnsibleModule $module
+
+# We need to validate the connection because WinINet will just silently continue even if the connection does not
+# already exist.
+if ($null -ne $connection -and -not [Ansible.WinINetProxy.WinINetProxy]::IsValidConnection($connection)) {
+    $module.FailJson("The connection '$connection' does not exist.")
+}
+
+$actual_proxy = New-Object -TypeName Ansible.WinINetProxy.WinINetProxy -ArgumentList @(,$connection)
+$module.Diff.before = @{
+    auto_config_url = $actual_proxy.AutoConfigUrl
+    auto_detect = $actual_proxy.AutoDetect
+    bypass = $actual_proxy.ProxyBypass
+    server = $actual_proxy.Proxy
+}
+
+# Make sure an empty string is converted to $null for easier comparisons
+if ([String]::IsNullOrEmpty($auto_config_url)) {
+    $auto_config_url = $null
+}
+if ([String]::IsNullOrEmpty($proxy)) {
+    $proxy = $null
+}
+if ([String]::IsNullOrEmpty($bypass)) {
+    $bypass = $null
+}
+
+# Record the original values in case we need to revert on a failure
+$previous_auto_config_url = $actual_proxy.AutoConfigUrl
+$previous_auto_detect = $actual_proxy.AutoDetect
+$previous_proxy = $actual_proxy.Proxy
+$previous_bypass = $actual_proxy.ProxyBypass
+
+$changed = $false
+if ($auto_config_url -ne $previous_auto_config_url) {
+    $actual_proxy.AutoConfigUrl = $auto_config_url
+    $changed = $true
+}
+
+if ($auto_detect -ne $previous_auto_detect) {
+    $actual_proxy.AutoDetect = $auto_detect
+    $changed = $true
+}
+
+if ($proxy -ne $previous_proxy) {
+    $actual_proxy.Proxy = $proxy
+    $changed = $true
+}
+
+if ($bypass -ne $previous_bypass) {
+    $actual_proxy.ProxyBypass = $bypass
+    $changed = $true
+}
+
+if ($changed -and -not $module.CheckMode) {
+    $actual_proxy.Set()
+
+    # Validate that the change was made correctly and revert if it wasn't. THe Set() method won't fail on invalid
+    # values so we need to check again to make sure all was good
+    $actual_proxy.Refresh()
+    if ($actual_proxy.AutoConfigUrl -ne $auto_config_url -or
+        $actual_proxy.AutoDetect -ne $auto_detect -or
+        $actual_proxy.Proxy -ne $proxy -or
+        $actual_proxy.ProxyBypass -ne $bypass) {
+
+        $actual_proxy.AutoConfigUrl = $previous_auto_config_url
+        $actual_proxy.AutoDetect = $previous_auto_detect
+        $actual_proxy.Proxy = $previous_proxy
+        $actual_proxy.ProxyBypass = $previous_bypass
+        $actual_proxy.Set()
+
+        $module.FailJson("Unknown error when trying to set auto_config_url '$auto_config_url', proxy '$proxy', or bypass '$bypass'")
+    }
+}
+$module.Result.changed = $changed
+
+$module.Diff.after = @{
+    auto_config_url = $auto_config_url
+    auto_detect = $auto_detect
+    bypass = $bypass
+    proxy = $proxy
+}
+
+$module.ExitJson()
diff --git a/lib/ansible/modules/windows/win_inet_proxy.py b/lib/ansible/modules/windows/win_inet_proxy.py
new file mode 100644
index 00000000000000..e4c7a57d43b090
--- /dev/null
+++ b/lib/ansible/modules/windows/win_inet_proxy.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: win_inet_proxy
+version_added: '2.8'
+short_description: Manages proxy settings for WinINet and Internet Explorer
+description:
+- Used to set, remote, or import proxy settings for Windows INet which includes
+  Internet Explorer.
+- WinINet is a framework used by interactive applications to submit web
+  requests through.
+- The proxy settings can also be used by other applications like Firefox,
+  Chrome, and others but there is no definitive list.
+options:
+  auto_detect:
+    description:
+    - Whether to configure WinINet to automatically detect proxy settings
+      through Web Proxy Auto-Detection C(WPAD).
+    - This corresponds to the checkbox I(Automatically detect settings) in the
+      connection settings window.
+    default: yes
+    type: bool
+  auto_config_url:
+    description:
+    - The URL of a proxy configuration script.
+    - Proxy configuration scripts are typically JavaScript files with the
+      C(.pac) extension that implement the C(FindProxyForURL(url, host)
+      function.
+    - Omit, set to null or an empty string to remove the auto config URL.
+    - This corresponds to the checkbox I(Use automatic configuration script) in
+      the connection settings window.
+    type: str
+  bypass:
+    description:
+    - A list of hosts that will bypass the set proxy when being accessed.
+    - Use C(<local>) to match hostnames that are not fully qualified domain
+      names. This is useful when needing to connect to intranet sites using
+      just the hostname. If defined, this should be the last entry in the
+      bypass list.
+    - Use C(<-loopback>) to stop automatically bypassing the proxy when
+      connecting through any loopback address like C(127.0.0.1), C(localhost),
+      or the local hostname.
+    - Omit, set to null or an empty string/list to remove the bypass list.
+    - If this is set then I(proxy) must also be set.
+    type: list
+  proxy:
+    description:
+    - A string or dict that specifies the proxy to be set.
+    - If setting a string, should be in the form C(hostname), C(hostname:port),
+      or C(protocol=hostname:port).
+    - If setting a dict, the keys relate are the protocol and the value is the
+      hostname and/or port for the protocol specified.
+    - Valid protocols are C(http), C(https), C(ftp), and C(socks).
+    - Omit, set to null or an empty string to remove the proxy settings.
+  connection:
+    description:
+    type: str
+notes:
+- This is not the same as the proxy settings set in WinHTTP through the
+  C(netsh) command. Use the M(win_http_proxy) module ot manage that instead.
+- These settings are by default set per user and not system wide. A registry
+  property must be set independently from this module if you wish to apply the
+  proxy for all users. See examples for more detail.
+- If per user proxy settings are desired, use I(become) to become any local
+  user on the host. No password is needed to be set for this to work.
+- If the proxy requires authentication, set the credentials using the
+  M(win_credential) module. This requires I(become) to be used so the
+  credential store can be accessed.
+seealso:
+- module: win_http_proxy
+- module: win_credential
+author:
+- Jordan Borean (@jborean93)
+'''
+
+EXAMPLES = r'''
+# This should be set before running the win_inet_proxy module
+- name: Configure IE proxy settings to apply to all users
+  win_regedit:
+    path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings
+    name: ProxySettingsPerUser
+    data: 0
+    type: dword
+    state: present
+
+# This should be set before running the win_inet_proxy module
+- name: Configure IE proxy settings to apply per user
+  win_regedit:
+    path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings
+    name: ProxySettingsPerUser
+    data: 1
+    type: dword
+    state: present
+
+- name: Configure IE proxy to use auto detected settings without an explicit proxy
+  win_inet_proxy:
+    auto_detect: yes
+
+- name: Configure IE proxy to use auto detected settings with a configuration script
+  win_inet_proxy:
+    auto_detect: yes
+    auto_config_url: http://proxy.ansible.com/proxy.pac
+
+- name: Configure IE to use explicit proxy host
+  win_inet_proxy:
+    auto_detect: yes
+    proxy: ansible.proxy
+
+- name: Configure IE to use explicit proxy host with port and without auto detection
+    auto_detect: no
+    proxy: ansible.proxy:8080
+
+- name: Configure IE to use a specific proxy per protocol
+  win_inet_proxy:
+    proxy:
+      http: ansible.proxy:8080
+      https: ansible.proxy:8443
+
+- name: Configure IE to use a specific proxy per protocol using a string
+  win_inet_proxy:
+    proxy: http=ansible.proxy:8080;https=ansible.proxy:8443
+
+- name: Set a proxy with a bypass list
+  win_inet_proxy:
+    proxy: ansible.proxy
+    bypass:
+    - server1
+    - server2
+    - <-loopback>
+    - <local>
+
+- name: Remove any explicit proxies that are set
+  win_inet_proxy:
+    proxy: ''
+    bypass: ''
+
+# This should be done after setting the IE proxy with win_inet_proxy
+- name: Import IE proxy configuration to WinHTTP
+  win_http_proxy:
+    source: ie
+'''
+
+RETURN = r'''
+#
+'''
diff --git a/test/integration/targets/win_http_proxy/aliases b/test/integration/targets/win_http_proxy/aliases
new file mode 100644
index 00000000000000..215e0b06920160
--- /dev/null
+++ b/test/integration/targets/win_http_proxy/aliases
@@ -0,0 +1 @@
+shippable/windows/group4
diff --git a/test/integration/targets/win_http_proxy/tasks/main.yml b/test/integration/targets/win_http_proxy/tasks/main.yml
new file mode 100644
index 00000000000000..5da9aa7fecb752
--- /dev/null
+++ b/test/integration/targets/win_http_proxy/tasks/main.yml
@@ -0,0 +1,14 @@
+---
+- name: make sure we start the tests with no proxy set
+  win_http_proxy:
+
+- block:
+  - name: run tests
+    include_tasks: tests.yml
+
+  always:
+  - name: remove any explicit proxy settings
+    win_http_proxy:
+
+  - name: reset WinINet proxy settings
+    win_inet_proxy:
diff --git a/test/integration/targets/win_http_proxy/tasks/tests.yml b/test/integration/targets/win_http_proxy/tasks/tests.yml
new file mode 100644
index 00000000000000..c68ea611b7202f
--- /dev/null
+++ b/test/integration/targets/win_http_proxy/tasks/tests.yml
@@ -0,0 +1,265 @@
+---
+- name: ensure we fail when proxy is not set with bypass
+  win_http_proxy:
+    bypass: abc
+  register: fail_bypass
+  failed_when: 'fail_bypass.msg != "missing parameter(s) required by ''bypass'': proxy"'
+
+- name: ensure we fail when proxy and source is set
+  win_http_proxy:
+    proxy: proxy
+    source: ie
+  register: fail_source
+  failed_when: 'fail_source.msg != "parameters are mutually exclusive: proxy, source"'
+
+- name: ensure we fail if an invalid protocol is specified
+  win_http_proxy:
+    proxy:
+      fail1: fail
+      fail2: fail
+  register: fail_protocol
+  failed_when: 'fail_protocol.msg != "Invalid keys found in proxy: fail1, fail2. Valid keys are http, https, ftp, socks."'
+
+# WinHTTP does not validate on set, this ensures the module checks and revert any failed attempts at setting the proxy
+# FIXME: Only certain hosts seem to have a strict winhttp definition, we can't run this in CI for now
+#- name: ensure we fail if invalid value is set
+#  win_http_proxy:
+#    proxy: fake=proxy
+#  register: fail_invalid
+#  failed_when: fail_invalid.msg != "Unknown error when trying to set proxy 'fake=proxy' or bypass ''"
+#
+#- name: check proxy is still set to Direct access
+#  win_command: netsh winhttp show proxy
+#  register: fail_invalid_actual
+#  failed_when: fail_invalid_actual.stdout_lines[3]|trim != "Direct access (no proxy server)."
+
+- name: set a proxy using a string (check)
+  win_http_proxy:
+    proxy: proxyhost
+  register: proxy_str_check
+  check_mode: True
+
+- name: get result of set a proxy using a string (check)
+  win_command: netsh winhttp show proxy
+  register: proxy_str_actual_check
+
+- name: assert set a proxy using a string (check)
+  assert:
+    that:
+    - proxy_str_check is changed
+    - proxy_str_actual_check.stdout_lines[3]|trim == "Direct access (no proxy server)."
+
+- name: set a proxy using a string
+  win_http_proxy:
+    proxy: proxyhost
+  register: proxy_str
+
+- name: get result of set a proxy using a string
+  win_command: netsh winhttp show proxy
+  register: proxy_str_actual
+
+- name: assert set a proxy using a string
+  assert:
+    that:
+    - proxy_str is changed
+    - 'proxy_str_actual.stdout_lines[3]|trim == "Proxy Server(s) :  proxyhost"'
+    - 'proxy_str_actual.stdout_lines[4]|trim == "Bypass List     :  (none)"'
+
+- name: set a proxy using a string (idempotent)
+  win_http_proxy:
+    proxy: proxyhost
+  register: proxy_str_again
+
+- name: assert set a proxy using a string (idempotent)
+  assert:
+    that:
+    - not proxy_str_again is changed
+
+- name: change a proxy and set bypass (check)
+  win_http_proxy:
+    proxy: proxyhost:8080
+    bypass:
+    - abc
+    - def
+    - <local>
+  register: change_proxy_check
+  check_mode: True
+
+- name: get result of change a proxy and set bypass (check)
+  win_command: netsh winhttp show proxy
+  register: change_proxy_actual_check
+
+- name: assert change a proxy and set bypass (check)
+  assert:
+    that:
+    - change_proxy_check is changed
+    - 'change_proxy_actual_check.stdout_lines[3]|trim == "Proxy Server(s) :  proxyhost"'
+    - 'change_proxy_actual_check.stdout_lines[4]|trim == "Bypass List     :  (none)"'
+
+- name: change a proxy and set bypass
+  win_http_proxy:
+    proxy: proxyhost:8080
+    bypass:
+    - abc
+    - def
+    - <local>
+  register: change_proxy
+
+- name: get result of change a proxy and set bypass
+  win_command: netsh winhttp show proxy
+  register: change_proxy_actual
+
+- name: assert change a proxy and set bypass
+  assert:
+    that:
+    - change_proxy is changed
+    - 'change_proxy_actual.stdout_lines[3]|trim == "Proxy Server(s) :  proxyhost:8080"'
+    - 'change_proxy_actual.stdout_lines[4]|trim == "Bypass List     :  abc;def;<local>"'
+
+- name: change a proxy and set bypass (idempotent)
+  win_http_proxy:
+    proxy: proxyhost:8080
+    bypass: abc,def,<local>
+  register: change_proxy_again
+
+- name: assert change a proxy and set bypass (idempotent)
+  assert:
+    that:
+    - not change_proxy_again is changed
+
+- name: change bypass list
+  win_http_proxy:
+    proxy: proxyhost:8080
+    bypass:
+    - abc
+    - <-loopback>
+  register: change_bypass
+
+- name: get result of change bypass list
+  win_command: netsh winhttp show proxy
+  register: change_bypass_actual
+
+- name: assert change bypass list
+  assert:
+    that:
+    - change_bypass is changed
+    - 'change_bypass_actual.stdout_lines[3]|trim == "Proxy Server(s) :  proxyhost:8080"'
+    - 'change_bypass_actual.stdout_lines[4]|trim == "Bypass List     :  abc;<-loopback>"'
+
+- name: remove proxy without options (check)
+  win_http_proxy:
+  register: remove_proxy_check
+  check_mode: yes
+
+- name: get result of remove proxy without options (check)
+  win_command: netsh winhttp show proxy
+  register: remove_proxy_actual_check
+
+- name: assert remove proxy without options (check)
+  assert:
+    that:
+    - remove_proxy_check is changed
+    - 'remove_proxy_actual_check.stdout_lines[3]|trim == "Proxy Server(s) :  proxyhost:8080"'
+    - 'remove_proxy_actual_check.stdout_lines[4]|trim == "Bypass List     :  abc;<-loopback>"'
+
+- name: remove proxy without options
+  win_http_proxy:
+  register: remove_proxy
+
+- name: get result of remove proxy without options
+  win_command: netsh winhttp show proxy
+  register: remove_proxy_actual
+
+- name: assert remove proxy without options
+  assert:
+    that:
+    - remove_proxy is changed
+    - remove_proxy_actual.stdout_lines[3]|trim == "Direct access (no proxy server)."
+
+- name: remove proxy without options (idempotent)
+  win_http_proxy:
+  register: remove_proxy_again
+
+- name: assert remove proxy without options (idempotent)
+  assert:
+    that:
+    - not remove_proxy_again is changed
+
+- name: set proxy with dictionary
+  win_http_proxy:
+    proxy:
+      http: proxy:8080
+      https: proxy:8443
+      ftp: proxy:821
+      socks: proxy:888
+  register: set_dict
+
+- name: get result of set proxy with dictionary
+  win_command: netsh winhttp show proxy
+  register: set_dict_actual
+
+- name: assert set proxy with dictionary
+  assert:
+    that:
+    - set_dict is changed
+    - 'set_dict_actual.stdout_lines[3]|trim == "Proxy Server(s) :  http=proxy:8080;https=proxy:8443;ftp=proxy:821;socks=proxy:888"'
+    - 'set_dict_actual.stdout_lines[4]|trim == "Bypass List     :  (none)"'
+
+- name: set proxy protocol with str
+  win_http_proxy:
+    proxy: http=proxy:8080;https=proxy:8443;ftp=proxy:821;socks=proxy:888
+  register: set_str_protocol
+
+- name: assert set proxy protocol with str
+  assert:
+    that:
+    - not set_str_protocol is changed
+
+- name: remove proxy with empty string
+  win_http_proxy:
+    proxy: ''
+  register: remove_empty_str
+
+- name: get result of remove proxy with empty string
+  win_command: netsh winhttp show proxy
+  register: remove_empty_str_actual
+
+- name: assert remove proxy with empty string
+  assert:
+    that:
+    - remove_empty_str is changed
+    - remove_empty_str_actual.stdout_lines[3]|trim == "Direct access (no proxy server)."
+
+- name: set explicit proxy for WinINet
+  win_inet_proxy:
+    proxy: proxyhost:8080
+    bypass:
+    - abc
+    - def
+    - <local>
+
+- name: import proxy from IE
+  win_http_proxy:
+    source: ie
+  register: import_ie
+
+- name: get result of import proxy from IE
+  win_command: netsh winhttp show proxy
+  register: import_ie_actual
+
+- name: assert import proxy from IE
+  assert:
+    that:
+    - import_ie is changed
+    - 'import_ie_actual.stdout_lines[3]|trim == "Proxy Server(s) :  proxyhost:8080"'
+    - 'import_ie_actual.stdout_lines[4]|trim == "Bypass List     :  abc;def;<local>"'
+
+- name: import proxy from IE (idempotent)
+  win_http_proxy:
+    source: ie
+  register: import_ie_again
+
+- name: assert import proxy from IE (idempotent)
+  assert:
+    that:
+    - not import_ie_again is changed
diff --git a/test/integration/targets/win_inet_proxy/aliases b/test/integration/targets/win_inet_proxy/aliases
new file mode 100644
index 00000000000000..215e0b06920160
--- /dev/null
+++ b/test/integration/targets/win_inet_proxy/aliases
@@ -0,0 +1 @@
+shippable/windows/group4
diff --git a/test/integration/targets/win_inet_proxy/library/win_inet_proxy_info.ps1 b/test/integration/targets/win_inet_proxy/library/win_inet_proxy_info.ps1
new file mode 100644
index 00000000000000..d52b11d3ad3758
--- /dev/null
+++ b/test/integration/targets/win_inet_proxy/library/win_inet_proxy_info.ps1
@@ -0,0 +1,275 @@
+#!powershell
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.AddType
+
+$spec = @{
+    options = @{
+        connection = @{ type = "str" }
+    }
+    supports_check_mode = $true
+}
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
+
+$connection = $module.Params.connection
+
+$win_inet_invoke = @'
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+
+namespace Ansible.WinINetProxyInfo
+{
+    internal class NativeHelpers
+    {
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class INTERNET_PER_CONN_OPTION_LISTW : IDisposable
+        {
+            public UInt32 dwSize;
+            public IntPtr pszConnection;
+            public UInt32 dwOptionCount;
+            public UInt32 dwOptionError;
+            public IntPtr pOptions;
+
+            public INTERNET_PER_CONN_OPTION_LISTW()
+            {
+                dwSize = (UInt32)Marshal.SizeOf(this);
+            }
+
+            public void Dispose()
+            {
+                if (pszConnection != IntPtr.Zero)
+                    Marshal.FreeHGlobal(pszConnection);
+                if (pOptions != IntPtr.Zero)
+                    Marshal.FreeHGlobal(pOptions);
+                GC.SuppressFinalize(this);
+            }
+            ~INTERNET_PER_CONN_OPTION_LISTW() { this.Dispose(); }
+        }
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class INTERNET_PER_CONN_OPTIONW : IDisposable
+        {
+            public INTERNET_PER_CONN_OPTION dwOption;
+            public ValueUnion Value;
+
+            [StructLayout(LayoutKind.Explicit)]
+            public class ValueUnion
+            {
+                [FieldOffset(0)]
+                public UInt32 dwValue;
+
+                [FieldOffset(0)]
+                public IntPtr pszValue;
+
+                [FieldOffset(0)]
+                public System.Runtime.InteropServices.ComTypes.FILETIME ftValue;
+            }
+
+            public void Dispose()
+            {
+                // We can't just check if Value.pszValue is not IntPtr.Zero as the union means it could be set even
+                // when the value is a UInt32 or FILETIME. We check against a known string option type and only free
+                // the value in those cases.
+                List<INTERNET_PER_CONN_OPTION> stringOptions = new List<INTERNET_PER_CONN_OPTION>
+                {
+                    { INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL },
+                    { INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS },
+                    { INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER }
+                };
+                if (Value != null && Value.pszValue != IntPtr.Zero && stringOptions.Contains(dwOption))
+                    Marshal.FreeHGlobal(Value.pszValue);
+                GC.SuppressFinalize(this);
+            }
+            ~INTERNET_PER_CONN_OPTIONW() { this.Dispose(); }
+        }
+
+        public enum INTERNET_OPTION : uint
+        {
+            INTERNET_OPTION_PER_CONNECTION_OPTION = 75,
+        }
+
+        public enum INTERNET_PER_CONN_OPTION : uint
+        {
+            INTERNET_PER_CONN_FLAGS = 1,
+            INTERNET_PER_CONN_PROXY_SERVER = 2,
+            INTERNET_PER_CONN_PROXY_BYPASS = 3,
+            INTERNET_PER_CONN_AUTOCONFIG_URL = 4,
+            INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5,
+            INTERNET_PER_CONN_FLAGS_UI = 10,  // IE8+ - Included with Windows 7 and Server 2008 R2
+        }
+
+        [Flags]
+        public enum PER_CONN_FLAGS : uint
+        {
+            PROXY_TYPE_DIRECT = 0x00000001,
+            PROXY_TYPE_PROXY = 0x00000002,
+            PROXY_TYPE_AUTO_PROXY_URL = 0x00000004,
+            PROXY_TYPE_AUTO_DETECT = 0x00000008,
+        }
+    }
+
+    internal class NativeMethods
+    {
+        [DllImport("Wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+        public static extern bool InternetQueryOptionW(
+            IntPtr hInternet,
+            NativeHelpers.INTERNET_OPTION dwOption,
+            SafeMemoryBuffer lpBuffer,
+            ref UInt32 lpdwBufferLength);
+    }
+
+    internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        public SafeMemoryBuffer() : base(true) { }
+        public SafeMemoryBuffer(int cb) : base(true)
+        {
+            base.SetHandle(Marshal.AllocHGlobal(cb));
+        }
+        public SafeMemoryBuffer(IntPtr handle) : base(true)
+        {
+            base.SetHandle(handle);
+        }
+
+        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+        protected override bool ReleaseHandle()
+        {
+            Marshal.FreeHGlobal(handle);
+            return true;
+        }
+    }
+
+    public class WinINetProxy
+    {
+        private string Connection;
+
+        public string AutoConfigUrl;
+        public bool AutoDetect;
+        public string Proxy;
+        public string ProxyBypass;
+
+        public WinINetProxy(string connection)
+        {
+            Connection = connection;
+            Refresh();
+        }
+
+        public void Refresh()
+        {
+            using (var connFlags = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS_UI))
+            using (var autoConfigUrl = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_AUTOCONFIG_URL))
+            using (var server = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_SERVER))
+            using (var bypass = CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_PROXY_BYPASS))
+            {
+                NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options = new NativeHelpers.INTERNET_PER_CONN_OPTIONW[]
+                {
+                    connFlags, autoConfigUrl, server, bypass
+                };
+
+                try
+                {
+                    QueryOption(options, Connection);
+                }
+                catch (Win32Exception e)
+                {
+                    if (e.NativeErrorCode == 87) // ERROR_INVALID_PARAMETER
+                    {
+                        // INTERNET_PER_CONN_FLAGS_UI only works for IE8+, try the fallback in case we are still working
+                        // with an ancient version.
+                        connFlags.dwOption = NativeHelpers.INTERNET_PER_CONN_OPTION.INTERNET_PER_CONN_FLAGS;
+                        QueryOption(options, Connection);
+                    }
+                    else
+                        throw;
+                }
+
+                NativeHelpers.PER_CONN_FLAGS flags = (NativeHelpers.PER_CONN_FLAGS)connFlags.Value.dwValue;
+
+                AutoConfigUrl = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_PROXY_URL)
+                    ? Marshal.PtrToStringUni(autoConfigUrl.Value.pszValue) : null;
+                AutoDetect = flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_AUTO_DETECT);
+                if (flags.HasFlag(NativeHelpers.PER_CONN_FLAGS.PROXY_TYPE_PROXY))
+                {
+                    Proxy = Marshal.PtrToStringUni(server.Value.pszValue);
+                    ProxyBypass = Marshal.PtrToStringUni(bypass.Value.pszValue);
+                }
+                else
+                {
+                    Proxy = null;
+                    ProxyBypass = null;
+                }
+            }
+        }
+
+        internal static NativeHelpers.INTERNET_PER_CONN_OPTIONW CreateConnOption(NativeHelpers.INTERNET_PER_CONN_OPTION option)
+        {
+            return new NativeHelpers.INTERNET_PER_CONN_OPTIONW
+            {
+                dwOption = option,
+                Value = new NativeHelpers.INTERNET_PER_CONN_OPTIONW.ValueUnion(),
+            };
+        }
+
+        internal static void QueryOption(NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection = null)
+        {
+            using (NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList = new NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW())
+            using (SafeMemoryBuffer optionListPtr = MarshalOptionList(optionList, options, connection))
+            {
+                UInt32 bufferSize = optionList.dwSize;
+                if (!NativeMethods.InternetQueryOptionW(
+                    IntPtr.Zero,
+                    NativeHelpers.INTERNET_OPTION.INTERNET_OPTION_PER_CONNECTION_OPTION,
+                    optionListPtr,
+                    ref bufferSize))
+                {
+                    throw new Win32Exception();
+                }
+
+                for (int i = 0; i < options.Length; i++)
+                {
+                    IntPtr opt = IntPtr.Add(optionList.pOptions, i * Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW)));
+                    NativeHelpers.INTERNET_PER_CONN_OPTIONW option = (NativeHelpers.INTERNET_PER_CONN_OPTIONW)Marshal.PtrToStructure(opt,
+                        typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
+                    options[i].Value = option.Value;
+                    option.Value = null;  // Stops the GC from freeing the same memory twice
+                }
+            }
+        }
+
+        internal static SafeMemoryBuffer MarshalOptionList(NativeHelpers.INTERNET_PER_CONN_OPTION_LISTW optionList,
+            NativeHelpers.INTERNET_PER_CONN_OPTIONW[] options, string connection)
+        {
+            optionList.pszConnection = Marshal.StringToHGlobalUni(connection);
+            optionList.dwOptionCount = (UInt32)options.Length;
+
+            int optionSize = Marshal.SizeOf(typeof(NativeHelpers.INTERNET_PER_CONN_OPTIONW));
+            optionList.pOptions = Marshal.AllocHGlobal(optionSize * options.Length);
+            for (int i = 0; i < options.Length; i++)
+            {
+                IntPtr option = IntPtr.Add(optionList.pOptions, i * optionSize);
+                Marshal.StructureToPtr(options[i], option, false);
+            }
+
+            SafeMemoryBuffer optionListPtr = new SafeMemoryBuffer((int)optionList.dwSize);
+            Marshal.StructureToPtr(optionList, optionListPtr.DangerousGetHandle(), false);
+            return optionListPtr;
+        }
+    }
+}
+'@
+Add-CSharpType -References $win_inet_invoke -AnsibleModule $module
+
+$proxy = New-Object -TypeName Ansible.WinINetProxyInfo.WinINetProxy -ArgumentList @(,$connection)
+$module.Result.auto_config_url = $proxy.AutoConfigUrl
+$module.Result.auto_detect = $proxy.AutoDetect
+$module.Result.proxy = $proxy.Proxy
+$module.Result.bypass = $proxy.ProxyBypass
+
+$module.ExitJson()
diff --git a/test/integration/targets/win_inet_proxy/library/win_phonebook_entry.ps1 b/test/integration/targets/win_inet_proxy/library/win_phonebook_entry.ps1
new file mode 100644
index 00000000000000..1746921f6d7ce0
--- /dev/null
+++ b/test/integration/targets/win_inet_proxy/library/win_phonebook_entry.ps1
@@ -0,0 +1,521 @@
+#!powershell
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.AddType
+
+# This is a very basic skeleton of a possible Windows module for managing RAS connections. It is mostly barebones
+# to enable testing for win_inet_proxy but I've done a bit of extra work in the PInvoke space to possible expand
+# sometime in the future.
+
+$spec = @{
+    options = @{
+        device_type = @{
+            type = "str"
+            choices = @("atm", "framerelay", "generic", "rda", "isdn", "modem", "pad",
+                "parallel", "pppoe", "vpn", "serial", "sonet", "sw56", "x25")
+        }
+        device_name = @{ type = "str" }
+        framing_protocol = @{ type = "str"; choices = @("ppp", "ras", "slip") }
+        name = @{ type = "str"; required = $true }
+        options = @{ type = "list" }
+        state = @{ type = "str"; choices = @("absent", "present"); default = "present" }
+        type = @{ type = "str"; choices = @("broadband", "direct", "phone", "vpn")}
+    }
+    required_if = @(
+        ,@("state", "present", @("type", "device_name", "device_type", "framing_protocol"))
+    )
+    supports_check_mode = $false
+}
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
+
+$device_type = $module.Params.device_type
+$device_name = $module.Params.device_name
+$framing_protocol = $module.Params.framing_protocol
+$name = $module.Params.name
+$options = $module.Params.options
+$state = $module.Params.state
+$type = $module.Params.type
+
+$module.Result.guid = [System.Guid]::Empty
+
+$win_ras_invoke = @'
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ansible.WinPhonebookEntry
+{
+    public class NativeHelpers
+    {
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        public class RASENTRYW
+        {
+            public UInt32 dwSize;
+            public RasEntryOptions dwfOptions;
+            public UInt32 dwCountryId;
+            public UInt32 dwCountryCode;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] public string szAreaCode;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] public string szLocalPhoneNumber;
+            public UInt32 dwAlternateOffset;
+            public RASIPADDR ipaddr;
+            public RASIPADDR ipaddrDns;
+            public RASIPADDR ipaddrDnsAlt;
+            public RASIPADDR ipaddrWins;
+            public RASIPADDR ipaddrWinsAlt;
+            public UInt32 dwFrameSize;
+            public RasNetProtocols dwfNetProtocols;
+            public RasFramingProtocol dwFramingProtocol;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szScript;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szAutodialDll;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szAutodialFunc;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)] public string szDeviceType;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] public string szDeviceName;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string szX25PadType;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 201)] public string szX25Address;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 201)] public string szX25Facilities;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 201)] public string szX25UserData;
+            public UInt32 dwChannels;
+            public UInt32 dwReserved1;
+            public UInt32 dwReserved2;
+            public UInt32 dwSubEntries;
+            public RasDialMode dwDialMode;
+            public UInt32 dwDialExtraPercent;
+            public UInt32 dwDialExtraSampleSeconds;
+            public UInt32 dwHangUpExtraPercent;
+            public UInt32 dwHangUpExtraSampleSeconds;
+            public UInt32 dwIdleDisconnectSeconds;
+            public RasEntryTypes dwType;
+            public RasEntryEncryption dwEntryptionType;
+            public UInt32 dwCustomAuthKey;
+            public Guid guidId;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szCustomDialDll;
+            public RasVpnStrategy dwVpnStrategy;
+            public RasEntryOptions2 dwfOptions2;
+            public UInt32 dwfOptions3;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string szDnsSuffix;
+            public UInt32 dwTcpWindowSize;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szPrerequisitePbk;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] public string szPrerequisiteEntry;
+            public UInt32 dwRedialCount;
+            public UInt32 dwRedialPause;
+            public RASIPV6ADDR ipv6addrDns;
+            public RASIPV6ADDR ipv6addrDnsAlt;
+            public UInt32 dwIPv4InterfaceMatrix;
+            public UInt32 dwIPv6InterfaceMatrix;
+            // Server 2008 R2 / Windows 7+
+            // We cannot include these fields when running in Server 2008 as it will break the SizeOf calc of the struct
+#if !LONGHORN
+            public RASIPV6ADDR ipv6addr;
+            public UInt32 dwIPv6PrefixLength;
+            public UInt32 dwNetworkOutageTime;
+#endif
+
+            public RASENTRYW()
+            {
+                this.dwSize = (UInt32)Marshal.SizeOf(this);
+            }
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct RASIPADDR
+        {
+            public byte a;
+            public byte b;
+            public byte c;
+            public byte d;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct RASIPV6ADDR
+        {
+            byte a;
+            byte b;
+            byte c;
+            byte d;
+            byte e;
+            byte f;
+            byte g;
+            byte h;
+            byte i;
+            byte j;
+            byte k;
+            byte l;
+            byte m;
+            byte n;
+            byte o;
+            byte p;
+        }
+
+        public enum RasDialMode : uint
+        {
+            RASEDM_DialAll = 1,
+            RASEDM_DialAsNeeded = 2,
+        }
+
+        public enum RasEntryEncryption : uint
+        {
+            ET_None = 0,
+            ET_Require = 1,
+            ET_RequireMax = 2,
+            ET_Optional = 3
+        }
+
+        [Flags]
+        public enum RasEntryOptions : uint
+        {
+            RASEO_UseCountryAndAreaCodes = 0x00000001,
+            RASEO_SpecificIpAddr = 0x00000002,
+            RASEO_SpecificNameServers = 0x00000004,
+            RASEO_IpHeaderCompression = 0x00000008,
+            RASEO_RemoteDefaultGateway = 0x00000010,
+            RASEO_DisableLcpExtensions = 0x00000020,
+            RASEO_TerminalBeforeDial = 0x00000040,
+            RASEO_TerminalAfterDial = 0x00000080,
+            RASEO_ModemLights = 0x00000100,
+            RASEO_SwCompression = 0x00000200,
+            RASEO_RequireEncrptedPw = 0x00000400,
+            RASEO_RequireMsEncrptedPw = 0x00000800,
+            RASEO_RequireDataEncrption = 0x00001000,
+            RASEO_NetworkLogon = 0x00002000,
+            RASEO_UseLogonCredentials = 0x00004000,
+            RASEO_PromoteAlternates = 0x00008000,
+            RASEO_SecureLocalFiles = 0x00010000,
+            RASEO_RequireEAP = 0x00020000,
+            RASEO_RequirePAP = 0x00040000,
+            RASEO_RequireSPAP = 0x00080000,
+            RASEO_Custom = 0x00100000,
+            RASEO_PreviewPhoneNumber = 0x00200000,
+            RASEO_SharedPhoneNumbers = 0x00800000,
+            RASEO_PreviewUserPw = 0x01000000,
+            RASEO_PreviewDomain = 0x02000000,
+            RASEO_ShowDialingProgress = 0x04000000,
+            RASEO_RequireCHAP = 0x08000000,
+            RASEO_RequireMsCHAP = 0x10000000,
+            RASEO_RequireMsCHAP2 = 0x20000000,
+            RASEO_RequireW95MSCHAP = 0x40000000,
+            RASEO_CustomScript = 0x80000000,
+        }
+
+        [Flags]
+        public enum RasEntryOptions2 : uint
+        {
+            RASEO2_None = 0x00000000,
+            RASEO2_SecureFileAndPrint = 0x00000001,
+            RASEO2_SecureClientForMSNet = 0x00000002,
+            RASEO2_DontNegotiateMultilink = 0x00000004,
+            RASEO2_DontUseRasCredentials = 0x00000008,
+            RASEO2_UsePreSharedKey = 0x00000010,
+            RASEO2_Internet = 0x00000020,
+            RASEO2_DisableNbtOverIP = 0x00000040,
+            RASEO2_UseGlobalDeviceSettings = 0x00000080,
+            RASEO2_ReconnectIfDropped = 0x00000100,
+            RASEO2_SharePhoneNumbers = 0x00000200,
+            RASEO2_SecureRoutingCompartment = 0x00000400,
+            RASEO2_UseTypicalSettings = 0x00000800,
+            RASEO2_IPv6SpecificNameServers = 0x00001000,
+            RASEO2_IPv6RemoteDefaultGateway = 0x00002000,
+            RASEO2_RegisterIpWithDNS = 0x00004000,
+            RASEO2_UseDNSSuffixForRegistration = 0x00008000,
+            RASEO2_IPv4ExplicitMetric = 0x00010000,
+            RASEO2_IPv6ExplicitMetric = 0x00020000,
+            RASEO2_DisableIKENameEkuCheck = 0x00040000,
+            // Server 2008 R2 / Windows 7+
+            RASEO2_DisableClassBasedStaticRoute = 0x00800000,
+            RASEO2_SpecificIPv6Addr = 0x01000000,
+            RASEO2_DisableMobility = 0x02000000,
+            RASEO2_RequireMachineCertificates = 0x04000000,
+            // Server 2012 / Windows 8+
+            RASEO2_UsePreSharedKeyForIkev2Initiator = 0x00800000,
+            RASEO2_UsePreSharedKeyForIkev2Responder = 0x01000000,
+            RASEO2_CacheCredentials = 0x02000000,
+            // Server 2012 R2 / Windows 8.1+
+            RASEO2_AutoTriggerCapable = 0x04000000,
+            RASEO2_IsThirdPartyProfile = 0x08000000,
+            RASEO2_AuthTypeIsOtp = 0x10000000,
+            // Server 2016 / Windows 10+
+            RASEO2_IsAlwaysOn = 0x20000000,
+            RASEO2_IsPrivateNetwork = 0x40000000,
+        }
+
+        public enum RasEntryTypes : uint
+        {
+            RASET_Phone = 1,
+            RASET_Vpn = 2,
+            RASET_Direct = 3,
+            RASET_Internet = 4,
+            RASET_Broadband = 5,
+        }
+
+        public enum RasFramingProtocol : uint
+        {
+            RASFP_Ppp = 0x00000001,
+            RASFP_Slip = 0x00000002,
+            RASFP_Ras = 0x00000004
+        }
+
+        [Flags]
+        public enum RasNetProtocols : uint
+        {
+            RASNP_NetBEUI = 0x00000001,
+            RASNP_Ipx = 0x00000002,
+            RASNP_Ip = 0x00000004,
+            RASNP_Ipv6 = 0x00000008
+        }
+
+        public enum RasVpnStrategy : uint
+        {
+            VS_Default = 0,
+            VS_PptpOnly = 1,
+            VS_PptpFirst = 2,
+            VS_L2tpOnly = 3,
+            VS_L2tpFirst = 4,
+            VS_SstpOnly = 5,
+            VS_SstpFirst = 6,
+            VS_Ikev2Only = 7,
+            VS_Ikev2First = 8,
+            VS_GREOnly = 9,
+            VS_PptpSstp = 12,
+            VS_L2tpSstp = 13,
+            VS_Ikev2Sstp = 14,
+        }
+    }
+
+    internal class NativeMethods
+    {
+        [DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
+        public static extern UInt32 RasDeleteEntryW(
+            string lpszPhonebook,
+            string lpszEntry);
+
+        [DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
+        public static extern UInt32 RasGetEntryPropertiesW(
+            string lpszPhonebook,
+            string lpszEntry,
+            [In, Out] NativeHelpers.RASENTRYW lpRasEntry,
+            ref UInt32 dwEntryInfoSize,
+            IntPtr lpbDeviceInfo,
+            ref UInt32 dwDeviceInfoSize);
+
+        [DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
+        public static extern UInt32 RasSetEntryPropertiesW(
+            string lpszPhonebook,
+            string lpszEntry,
+            NativeHelpers.RASENTRYW lpRasEntry,
+            UInt32 dwEntryInfoSize,
+            IntPtr lpbDeviceInfo,
+            UInt32 dwDeviceInfoSize);
+
+        [DllImport("Rasapi32.dll", CharSet = CharSet.Unicode)]
+        public static extern UInt32 RasValidateEntryNameW(
+            string lpszPhonebook,
+            string lpszEntry);
+    }
+
+    public class Phonebook
+    {
+        public static void CreateEntry(string entry, NativeHelpers.RASENTRYW details)
+        {
+            UInt32 res = NativeMethods.RasSetEntryPropertiesW(null, entry, details,
+                details.dwSize, IntPtr.Zero, 0);
+
+            if (res != 0)
+                throw new Exception(String.Format("RasSetEntryPropertiesW({0}) failed {1}", entry, res));
+        }
+
+        public static void DeleteEntry(string entry)
+        {
+            UInt32 res = NativeMethods.RasDeleteEntryW(null, entry);
+            if (res != 0)
+                throw new Exception(String.Format("RasDeleteEntryW({0}) failed {1}", entry, res));
+        }
+
+        public static NativeHelpers.RASENTRYW GetEntry(string entry)
+        {
+            NativeHelpers.RASENTRYW details = new NativeHelpers.RASENTRYW();
+            UInt32 dwEntryInfoSize = details.dwSize;
+            UInt32 dwDeviceInfoSize = 0;
+
+            UInt32 res = NativeMethods.RasGetEntryPropertiesW(null, entry, details, ref dwEntryInfoSize,
+                IntPtr.Zero, ref dwDeviceInfoSize);
+
+            if (res != 0)
+                throw new Exception(String.Format("RasGetEntryPropertiesW({0}) failed {1}", entry, res));
+
+            return details;
+        }
+
+        public static bool IsValidEntry(string entry)
+        {
+            // 183 == ENTRY_ALREADY_EXISTS
+            return NativeMethods.RasValidateEntryNameW(null, entry) == 183;
+        }
+    }
+}
+'@
+
+$add_type_params = @{
+    Reference = $win_ras_invoke
+    AnsibleModule = $module
+}
+# We need to set a custom compile option when running on Server 2008 due to the change in the RASENTRYW structure
+$os_version = [Version](Get-Item -LiteralPath $env:SystemRoot\System32\kernel32.dll).VersionInfo.ProductVersion
+if ($os_version -lt [Version]"6.1") {
+    $add_type_params.CompileSymbols = @("LONGHORN")
+}
+Add-CSharpType @add_type_params
+
+$exists = [Ansible.WinPhonebookEntry.Phonebook]::IsValidEntry($name)
+if ($exists) {
+    $entry = [Ansible.WinPhonebookEntry.Phonebook]::GetEntry($name)
+    $module.Result.guid = $entry.guidId
+}
+
+if ($state -eq "present") {
+    # Convert the input values to enum values
+    $expected_type = switch ($type) {
+        "broadband" { [Ansible.WinPhonebookEntry.NativeHelpers+RasEntryTypes]::RASET_Broadband }
+        "direct" { [Ansible.WinPhonebookEntry.NativeHelpers+RasEntryTypes]::RASET_Direct }
+        "phone" { [Ansible.WinPhonebookEntry.NativeHelpers+RasEntryTypes]::RASET_Phone }
+        "vpn" { [Ansible.WinPhonebookEntry.NativeHelpers+RasEntryTypes]::RASET_Vpn }
+    }
+
+    $expected_framing_protocol = switch ($framing_protocol) {
+        "ppp" { [Ansible.WinPhonebookEntry.NativeHelpers+RasFramingProtocol]::RASFP_Ppp }
+        "ras" { [Ansible.WinPhonebookEntry.NativeHelpers+RasFramingProtocol]::RASFP_Ras }
+        "slip" { [Ansible.WinPhonebookEntry.NativeHelpers+RasFramingProtocol]::RASFP_Slip }
+    }
+
+    $expected_options1 = [System.Collections.Generic.List`1[String]]@()
+    $expected_options2 = [System.Collections.Generic.List`1[String]]@()
+    $invalid_options = [System.Collections.Generic.List`1[String]]@()
+    foreach ($option in $options) {
+        # See https://msdn.microsoft.com/en-us/25c46850-4fb7-47a9-9645-139f0e869559 for more info on the options
+        # TODO: some of these options are set to indicate entries in RASENTRYW, we should automatically add them
+        # based on the input values.
+        switch ($option) {
+            # dwfOptions
+            "use_country_and_area_codes" { $expected_options1.Add("RASEO_UseCountryAndAreaCode") }
+            "specific_ip_addr" { $expected_options1.Add("RASEO_SpecificIpAddr") }
+            "specific_name_servers" { $expected_options1.Add("RASEO_SpecificNameServers") }
+            "ip_header_compression" { $expected_options1.Add("RASEO_IpHeaderCompression") }
+            "remote_default_gateway" { $expected_options1.Add("RASEO_RemoteDefaultGateway") }
+            "disable_lcp_extensions" { $expected_options1.Add("RASEO_DisableLcpExtensions") }
+            "terminal_before_dial" { $expected_options1.Add("RASEO_TerminalBeforeDial") }
+            "terminal_after_dial" { $expected_options1.Add("RASEO_TerminalAfterDial") }
+            "modem_lights" { $expected_options1.Add("RASEO_ModemLights") }
+            "sw_compression" { $expected_options1.Add("RASEO_SwCompression") }
+            "require_encrypted_password" { $expected_options1.Add("RASEO_RequireEncrptedPw") }
+            "require_ms_encrypted_password" { $expected_options1.Add("RASEO_RequireMsEncrptedPw") }
+            "require_data_encryption" { $expected_options1.Add("RASEO_RequireDataEncrption") }
+            "network_logon" { $expected_options1.Add("RASEO_NetworkLogon") }
+            "use_logon_credentials" { $expected_options1.Add("RASEO_UseLogonCredentials") }
+            "promote_alternates" { $expected_options1.Add("RASEO_PromoteAlternates") }
+            "secure_local_files" { $expected_options1.Add("RASEO_SecureLocalFiles") }
+            "require_eap" { $expected_options1.Add("RASEO_RequireEAP") }
+            "require_pap" { $expected_options1.Add("RASEO_RequirePAP") }
+            "require_spap" { $expected_options1.Add("RASEO_RequireSPAP") }
+            "custom" { $expected_options1.Add("RASEO_Custom") }
+            "preview_phone_number" { $expected_options1.Add("RASEO_PreviewPhoneNumber") }
+            "shared_phone_numbers" { $expected_options1.Add("RASEO_SharedPhoneNumbers") }
+            "preview_user_password" { $expected_options1.Add("RASEO_PreviewUserPw") }
+            "preview_domain" { $expected_options1.Add("RASEO_PreviewDomain") }
+            "show_dialing_progress" { $expected_options1.Add("RASEO_ShowDialingProgress") }
+            "require_chap" { $expected_options1.Add("RASEO_RequireCHAP") }
+            "require_ms_chap" { $expected_options1.Add("RASEO_RequireMsCHAP") }
+            "require_ms_chap2" { $expected_options1.Add("RASEO_RequireMsCHAP2") }
+            "require_w95_ms_chap" { $expected_options1.Add("RASEO_RequireW95MSCHAP") }
+            "custom_script" { $expected_options1.Add("RASEO_CustomScript") }
+            # dwfOptions2
+            "secure_file_and_print" { $expected_options2.Add("RASEO2_SecureFileAndPrint") }
+            "secure_client_for_ms_net" { $expected_options2.Add("RASEO2_SecureClientForMSNet") }
+            "dont_negotiate_multilink" { $expected_options2.Add("RASEO2_DontNegotiateMultilink") }
+            "dont_use_ras_credential" { $expected_options2.Add("RASEO2_DontUseRasCredentials") }
+            "use_pre_shared_key" { $expected_options2.Add("RASEO2_UsePreSharedKey") }
+            "internet" { $expected_options2.Add("RASEO2_Internet") }
+            "disable_nbt_over_ip" { $expected_options2.Add("RASEO2_DisableNbtOverIP") }
+            "use_global_device_settings" { $expected_options2.Add("RASEO2_UseGlobalDeviceSettings") }
+            "reconnect_if_dropped" { $expected_options2.Add("RASEO2_ReconnectIfDropped") }
+            "share_phone_numbers" { $expected_options2.Add("RASEO2_SharePhoneNumbers") }
+            "secure_routing_compartment" { $expected_options2.Add("RASEO2_SecureRoutingCompartment") }
+            "use_typical_settings" { $expected_options2.Add("RASEO2_UseTypicalSettings") }
+            "ipv6_specific_name_servers" { $expected_options2.Add("RASEO2_IPv6SpecificNameServers") }
+            "ipv6_remote_default_gateway" { $expected_options2.Add("RASEO2_IPv6RemoteDefaultGateway") }
+            "register_ip_with_dns" { $expected_options2.Add("RASEO2_RegisterIpWithDNS") }
+            "use_dns_suffix_for_registration" { $expected_options2.Add("RASEO2_UseDNSSuffixForRegistration") }
+            "ipv4_explicit_metric" { $expected_options2.Add("RASEO2_IPv4ExplicitMetric") }
+            "ipv6_explicit_metric" { $expected_options2.Add("RASEO2_IPv6ExplicitMetric") }
+            "disable_ike_name_eku_check" { $expected_options2.Add("RASEO2_DisableIKENameEkuCheck") }
+            # TODO: Version check for the below, OS Version >= 6.1
+            "disable_class_based_static_route" { $expected_options2.Add("RASEO2_DisableClassBasedStaticRoute") }
+            "specific_ipv6_addr" { $expected_options2.Add("RASEO2_SpecificIPv6Addr") }
+            "disable_mobility" { $expected_options2.Add("RASEO2_DisableMobility") }
+            "require_machine_certificates" { $expected_options2.Add("RASEO2_RequireMachineCertificates") }
+            # TODO: Version check for the below, OS Version >= 6.2
+            "use_pre_shared_key_for_ikev2_initiator" { $expected_options2.Add("RASEO2_UsePreSharedKeyForIkev2Initiator") }
+            "use_pre_shared_key_for_ikev2_responder" { $expected_options2.Add("RASEO2_UsePreSharedKeyForIkev2Responder") }
+            "cache_credentials" { $expected_options2.Add("RASEO2_CacheCredentials") }
+            # TODO: Version check for the below, OS Version >= 6.3
+            "auto_trigger_capable" { $expected_options2.Add("RASEO2_AutoTriggerCapable") }
+            "is_third_party_profile" { $expected_options2.Add("RASEO2_IsThirdPartyProfile") }
+            "auth_type_is_otp" { $expected_options2.Add("RASEO2_AuthTypeIsOtp") }
+            # TODO: Version check for the below, OS Version >= 10.0
+            "is_always_on" { $expected_options2.Add("RASEO2_IsAlwaysOn") }
+            "is_private_network" { $expected_options2.Add("RASEO2_IsPrivateNetwork") }
+            default { $invalid_options.Add($option) }
+        }
+    }
+    if ($invalid_options.Count -gt 0) {
+        $module.FailJson("Encountered invalid options: $($invalid_options -join ", ")")
+    }
+    $expected_options1 = [Ansible.WinPhonebookEntry.NativeHelpers+RasEntryOptions]($expected_options1 -join ", ")
+    $expected_options2 = [Ansible.WinPhonebookEntry.NativeHelpers+RasEntryOptions2]($expected_options2 -join ", ")
+
+    $property_map = @{
+        szDeviceName = $device_name
+        szDeviceType = $device_type
+        dwFramingProtocol = $expected_framing_protocol
+        dwfOptions = $expected_options1
+        dwfOptions2 = $expected_options2
+        dwType = $expected_type
+    }
+
+    if (-not $exists) {
+        $entry = New-Object -TypeName Ansible.WinPhonebookEntry.NativeHelpers+RASENTRYW
+        foreach ($kvp in $property_map.GetEnumerator()) {
+            $entry."$($kvp.Key)" = $kvp.Value
+        }
+
+        [Ansible.WinPhonebookEntry.Phonebook]::CreateEntry($name, $entry)
+        $module.Result.changed = $true
+
+        # Once created we then get the entry object again to retrieve the unique GUID ID to return
+        $entry = [Ansible.WinPhonebookEntry.Phonebook]::GetEntry($name)
+        $module.Result.guid = $entry.guidId
+    } else {
+        $entry = [Ansible.WinPhonebookEntry.Phonebook]::GetEntry($name)
+        $changed = $false
+        foreach ($kvp in $property_map.GetEnumerator()) {
+            $key = $kvp.Key
+            $actual_value = $entry.$key
+            if ($actual_value -ne $kvp.Value) {
+                $entry.$key = $kvp.Value
+                $changed = $true
+            }
+        }
+
+        if ($changed) {
+            [Ansible.WinPhonebookEntry.Phonebook]::CreateEntry($name, $entry)
+            $module.Result.changed = $true
+        }
+    }
+} else {
+    if ($exists) {
+        [Ansible.WinPhonebookEntry.Phonebook]::DeleteEntry($name)
+        $module.Result.changed = $true
+    }
+}
+
+$module.ExitJson()
diff --git a/test/integration/targets/win_inet_proxy/tasks/main.yml b/test/integration/targets/win_inet_proxy/tasks/main.yml
new file mode 100644
index 00000000000000..f92a60eab57de9
--- /dev/null
+++ b/test/integration/targets/win_inet_proxy/tasks/main.yml
@@ -0,0 +1,16 @@
+---
+- name: make sure we start the tests with the defaults
+  win_inet_proxy:
+
+- block:
+  - name: run tests
+    include_tasks: tests.yml
+
+  always:
+  - name: reset proxy back to defaults
+    win_inet_proxy:
+
+  - name: remove phonebook entry
+    win_phonebook_entry:
+      name: Ansible Test Dialup
+      state: absent
diff --git a/test/integration/targets/win_inet_proxy/tasks/tests.yml b/test/integration/targets/win_inet_proxy/tasks/tests.yml
new file mode 100644
index 00000000000000..47ddfbe2e802a1
--- /dev/null
+++ b/test/integration/targets/win_inet_proxy/tasks/tests.yml
@@ -0,0 +1,308 @@
+---
+- name: ensure we fail when proxy is not set with bypass
+  win_inet_proxy:
+    bypass: abc
+  register: fail_bypass
+  failed_when: 'fail_bypass.msg != "missing parameter(s) required by ''bypass'': proxy"'
+
+- name: ensure we fail if an invalid protocol is specified
+  win_inet_proxy:
+    proxy:
+      fail1: fail
+      fail2: fail
+  register: fail_proxy
+  failed_when: 'fail_proxy.msg != "Invalid keys found in proxy: fail1, fail2. Valid keys are http, https, ftp, socks."'
+
+- name: ensure we fail if invalid value is set
+  win_inet_proxy:
+    proxy: fake=proxy
+  register: fail_invalid
+  failed_when: fail_invalid.msg != "Unknown error when trying to set auto_config_url '', proxy 'fake=proxy', or bypass ''"
+
+- name: ensure we fail if an invalid connection is set
+  win_inet_proxy:
+    connection: Fake Connection
+  register: fail_connection
+  failed_when: fail_connection.msg != "The connection 'Fake Connection' does not exist."
+
+- name: check proxy is still set to Direct access
+  win_inet_proxy_info:
+  register: fail_invalid_actual
+  failed_when: fail_invalid_actual.proxy == 'fake=proxy'
+
+- name: disable auto detect (check)
+  win_inet_proxy:
+    auto_detect: no
+  register: disable_auto_detect_check
+  check_mode: yes
+
+- name: get result of disable auto detect (check)
+  win_inet_proxy_info:
+  register: disable_auto_detect_actual_check
+
+- name: assert disable auto detect (check)
+  assert:
+    that:
+    - disable_auto_detect_check is changed
+    - disable_auto_detect_actual_check.auto_detect
+
+- name: disable auto detect
+  win_inet_proxy:
+    auto_detect: no
+  register: disable_auto_detect
+
+- name: get result of disable auto detect
+  win_inet_proxy_info:
+  register: disable_auto_detect_actual
+
+- name: assert disable auto detect
+  assert:
+    that:
+    - disable_auto_detect is changed
+    - not disable_auto_detect_actual.auto_detect
+
+- name: disable auto detect (idempotent)
+  win_inet_proxy:
+    auto_detect: no
+  register: disable_auto_detect_again
+
+- name: assert disable auto detect (idempotent)
+  assert:
+    that:
+    - not disable_auto_detect_again is changed
+
+- name: set auto config url
+  win_inet_proxy:
+    auto_config_url: http://ansible.com/proxy.pac
+  register: set_auto_url
+
+- name: get result of set auto config url
+  win_inet_proxy_info:
+  register: set_auto_url_actual
+
+- name: assert set auto config url
+  assert:
+    that:
+    - set_auto_url is changed
+    - set_auto_url_actual.auto_detect
+    - set_auto_url_actual.auto_config_url == 'http://ansible.com/proxy.pac'
+
+- name: set auto config url (idempotent)
+  win_inet_proxy:
+    auto_config_url: http://ansible.com/proxy.pac
+  register: set_auto_url_again
+
+- name: set auto config url (idempotent)
+  assert:
+    that:
+    - not set_auto_url_again is changed
+
+- name: set a proxy using a string
+  win_inet_proxy:
+    proxy: proxyhost
+  register: proxy_str
+
+- name: get result of set a proxy using a string
+  win_inet_proxy_info:
+  register: proxy_str_actual
+
+- name: assert set a proxy using a string
+  assert:
+    that:
+    - proxy_str is changed
+    - proxy_str_actual.auto_detect
+    - proxy_str_actual.auto_config_url == None
+    - proxy_str_actual.proxy == 'proxyhost'
+
+- name: set a proxy using a string (idempotent)
+  win_inet_proxy:
+    proxy: proxyhost
+  register: proxy_str_again
+
+- name: assert set a proxy using a string (idempotent)
+  assert:
+    that:
+    - not proxy_str_again is changed
+
+- name: change a proxy and set bypass
+  win_inet_proxy:
+    proxy: proxyhost:8080
+    bypass:
+    - abc
+    - def
+    - <local>
+  register: change_proxy
+
+- name: get result of change a proxy and set bypass
+  win_inet_proxy_info:
+  register: change_proxy_actual
+
+- name: assert change a proxy and set bypass
+  assert:
+    that:
+    - change_proxy is changed
+    - change_proxy_actual.proxy == 'proxyhost:8080'
+    - change_proxy_actual.bypass == 'abc;def;<local>'
+
+- name: change a proxy and set bypass (idempotent)
+  win_inet_proxy:
+    proxy: proxyhost:8080
+    bypass: abc,def,<local>
+  register: change_proxy_again
+
+- name: assert change a proxy and set bypass (idempotent)
+  assert:
+    that:
+    - not change_proxy_again is changed
+
+- name: change bypass list
+  win_inet_proxy:
+    proxy: proxyhost:8080
+    bypass:
+    - abc
+    - <-loopback>
+  register: change_bypass
+
+- name: get reuslt of change bypass list
+  win_inet_proxy_info:
+  register: change_bypass_actual
+
+- name: assert change bypass list
+  assert:
+    that:
+    - change_bypass is changed
+    - change_bypass_actual.proxy == 'proxyhost:8080'
+    - change_bypass_actual.bypass == 'abc;<-loopback>'
+
+- name: remove proxy without options
+  win_inet_proxy:
+  register: remove_proxy
+
+- name: get result of remove proxy without options
+  win_inet_proxy_info:
+  register: remove_proxy_actual
+
+- name: assert remove proxy without options
+  assert:
+    that:
+    - remove_proxy is changed
+    - remove_proxy_actual.auto_detect == True
+    - remove_proxy_actual.auto_config_url == None
+    - remove_proxy_actual.proxy == None
+    - remove_proxy_actual.bypass == None
+
+- name: remove proxy without options (idempotent)
+  win_inet_proxy:
+  register: remove_proxy_again
+
+- name: assert remove proxy without options (idempotent)
+  assert:
+    that:
+    - not remove_proxy_again is changed
+
+- name: set proxy with dictionary
+  win_inet_proxy:
+    proxy:
+      http: proxy:8080
+      https: proxy:8443
+      ftp: proxy:821
+      socks: proxy:888
+  register: set_dict
+
+- name: get result of set proxy with dictionary
+  win_inet_proxy_info:
+  register: set_dict_actual
+
+- name: assert set proxy with dictionary
+  assert:
+    that:
+    - set_dict is changed
+    - set_dict_actual.proxy == 'http=proxy:8080;https=proxy:8443;ftp=proxy:821;socks=proxy:888'
+
+- name: set proxy protocol with str
+  win_inet_proxy:
+    proxy: http=proxy:8080;https=proxy:8443;ftp=proxy:821;socks=proxy:888
+  register: set_str_protocol
+
+- name: assert set proxy protocol with str
+  assert:
+    that:
+    - not set_str_protocol is changed
+
+- name: remove proxy with empty string
+  win_inet_proxy:
+    proxy: ''
+  register: remove_empty_str
+
+- name: get result of remove proxy with empty string
+  win_inet_proxy_info:
+  register: remove_empty_str_actual
+
+- name: assert remove proxy with empty string
+  assert:
+    that:
+    - remove_empty_str is changed
+    - remove_empty_str_actual.proxy == None
+
+- name: create test phonebook entry
+  win_phonebook_entry:
+    name: Ansible Test Dialup
+    device_type: pppoe
+    device_name: WAN Miniport (PPPOE)
+    framing_protocol: ppp
+    options:
+    - remote_default_gateway
+    - require_pap
+    - internet
+    type: broadband
+    state: present
+
+- name: set proxy for specific connection
+  win_inet_proxy:
+    connection: Ansible Test Dialup
+    auto_detect: no
+    auto_config_url: proxy.com
+    proxy: proxyhost:8080
+    bypass: proxyhost
+  register: set_connection
+
+- name: get result for set proxy for specific connection
+  win_inet_proxy_info:
+    connection: Ansible Test Dialup
+  register: set_connection_actual
+
+- name: get result for LAN connection proxy
+  win_inet_proxy_info:
+  register: set_connection_lan_actual
+
+- name: assert set proxy for specific connection
+  assert:
+    that:
+    - set_connection is changed
+    - set_connection_actual.auto_detect == False
+    - set_connection_actual.auto_config_url == 'proxy.com'
+    - set_connection_actual.proxy == 'proxyhost:8080'
+    - set_connection_actual.bypass == 'proxyhost'
+    - set_connection_lan_actual.auto_detect == True
+    - set_connection_lan_actual.auto_config_url == None
+    - set_connection_lan_actual.proxy == None
+    - set_connection_lan_actual.bypass == None
+
+- name: remove proxy for specific connection
+  win_inet_proxy:
+    connection: Ansible Test Dialup
+  register: remove_connection
+
+- name: get result of remove proxy for specific connection
+  win_inet_proxy_info:
+    connection: Ansible Test Dialup
+  register: remove_connection_actual
+
+- name: assert remove proxy for specific connection
+  assert:
+    that:
+    - remove_connection is changed
+    - remove_connection_actual.auto_detect == True
+    - remove_connection_actual.auto_config_url == None
+    - remove_connection_actual.proxy == None
+    - remove_connection_actual.bypass == None


Code review comments:
diff_hunk: @@ -0,0 +1,264 @@
+#!powershell
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+#Requires -Module Ansible.ModuleUtils.AddType
+
+$spec = @{
+    options = @{
+        bypass = @{ type = "list" }
+        proxy = @{ type = "raw" }
+        source = @{ type = "str"; choices = @("ie") }
+    }
+    mutually_exclusive = @(
+        @("proxy", "source"),
+        @("bypass", "source")
+    )
+    required_by = @{
+        bypass = @("proxy")
+    }
+    supports_check_mode = $true
+}
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
+
+$proxy = $module.Params.proxy
+$bypass = $module.Params.bypass
+$source = $module.Params.source
+
+# Parse the raw value, it should be a Dictionary or String
+if ($proxy -is [System.Collections.IDictionary]) {
+    $valid_keys = [System.Collections.Generic.List`1[String]]@("http", "https", "ftp", "socks")
+    # Check to make sure we don't have any invalid keys in the dict
+    $invalid_keys = [System.Collections.Generic.List`1[String]]@()
+    foreach ($k in $proxy.Keys) {
+        if ($k -notin $valid_keys) {
+            $invalid_keys.Add($k)
+        }
+    }
+
+    if ($invalid_keys.Count -gt 0) {
+        $invalid_keys = $invalid_keys | Sort-Object  # So our test assertion doesn't fail due to random ordering
+        $module.FailJson("Invalid keys found in proxy: $($invalid_keys -join ', '). Valid keys are $($valid_keys -join ', ').")
+    }
+
+    # Build the proxy string in the form 'protocol=host;', the order of valid_keys is also important
+    $proxy_list = [System.Collections.Generic.List`1[String]]@()
+    foreach ($k in $valid_keys) {
+        if ($proxy.ContainsKey($k)) {
+            $proxy_list.Add("$k=$($proxy.$k)")
+        }
+    }
+    $proxy = $proxy_list -join ";"
+} elseif ($null -ne $proxy) {
+    $proxy = $proxy.ToString()
+}
+
+if ($bypass) {
+    $bypass = $bypass -join ';'
+}
path: lib/ansible/modules/windows/win_http_proxy.ps1
text: @user1:
Should probably fail if `$bypass` is set to something but `$proxy` is not. (The `required_by` check isn't that strict.)

@author:
Not sure what you mean, `required_by` does exactly that and it's part of the integration tests.

@user1:
I can double-check, but I thought setting proxy to an empty string would break the check.

@author:
Ah yes that's a good point, forgot that `proxy: ''` means no proxy at all. Will fix up.

diff_hunk: @@ -0,0 +1,103 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: win_http_proxy
+version_added: '2.8'
+short_description: Manages proxy settings for WinHTTP
+description:
+- Used to set, remote, or import proxy settings for Windows HTTP Services
+  C(WinHTTP).
+- WinHTTP is a framework used by applications or services, typically .NET
+  applications or non-interactive services, to make web requests.
+options:
+  bypass:
+    description:
+    - A list of hosts that will bypass the set proxy when being accessed.
+    - Use C(<local>) to match hostnames that are not fully qualified domain
+      names. This is useful when needing to connect to intranet sites using
+      just the hostname.
+    - Use C(<-loopback>) to stop automatically bypassing the proxy when
+      connecting through any loopback address like C(127.0.0.1), C(localhost),
+      or the local hostname.
path: lib/ansible/modules/windows/win_http_proxy.py
text: @user1:
Did some testing, but was _unable to validate_ this `<-loopback>` part, at least for winhttp.

In particular, on W2016, localhost and 127.0.0.1 destinations appear to ignore the proxy unconditionally, whether `<-loopback>` is in the bypass list or not. I'm also not sure my use of W2016 is a factor, since the MS docs seem to indicate it hasn't materially changed since Vista/2008. I have also not found any support for the idea that `<-loopback>` does anything in the MS docs I reviewed.

Note that I did [find a reference](https://docs.microsoft.com/en-us/windows/desktop/api/wininet/nf-wininet-internetopena) for this construct for wininet, but not for winhttp.

Is this a real thing?

@author:
It's definitely there for WinINet and I just assumed WinHTTP was the same but it seems like this isn't the case.

As you said I found the reference to `<-loopback>` in the `InternetOpen` function as well as https://blogs.msdn.microsoft.com/ieinternals/2013/10/11/understanding-web-proxy-configuration/. This then has a link to has a link to http://blogs.msdn.com/b/fiddler/archive/2011/02/10/fiddler-is-better-with-internet-explorer-9.aspx but like most MS articles I come across these days it is no longer valid.

Considering this was a feature added in IE9, your testing indicates it doesn't work for WinHTTP, and the lack of docs for this in WinHTTP I will just assume that it isn't valid and remove the references to it in the docs for `win_http_proxy`.

I'll also update the documentation for `win_inet_proxy` to say you need IE9 for this to work.

diff_hunk: @@ -0,0 +1,103 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: win_http_proxy
+version_added: '2.8'
+short_description: Manages proxy settings for WinHTTP
+description:
+- Used to set, remote, or import proxy settings for Windows HTTP Services
path: lib/ansible/modules/windows/win_http_proxy.py
text: @user1:
`remote` -> `remove`?

@author:
Thanks, fixed.

diff_hunk: @@ -0,0 +1,154 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = r'''
+---
+module: win_inet_proxy
+version_added: '2.8'
+short_description: Manages proxy settings for WinINet and Internet Explorer
+description:
+- Used to set, remote, or import proxy settings for Windows INet which includes
+  Internet Explorer.
path: lib/ansible/modules/windows/win_inet_proxy.py
text: @user1:
`Used to set or remove proxy settings...`
* `remote` -> `remove`
* There is no importing IE settings here