diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0048bf7c..e8997079 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -317,6 +317,7 @@ if(MSVC)
         "${PROJECT_SOURCE_DIR}/msvc/getopt.c"
         "${PROJECT_SOURCE_DIR}/msvc/gettimeofday.c"
         "${PROJECT_SOURCE_DIR}/msvc/usleep.cpp"
+        "${PROJECT_SOURCE_DIR}/msvc/usb_com_helper.cpp"
         )
     set(EXTRA_WINDOWS_INCLUDES ${EXTRA_WINDOWS_INCLUDES}
         "${PROJECT_SOURCE_DIR}/msvc"
diff --git a/src/msvc/usb_com_helper.cpp b/src/msvc/usb_com_helper.cpp
new file mode 100644
index 00000000..9a718635
--- /dev/null
+++ b/src/msvc/usb_com_helper.cpp
@@ -0,0 +1,82 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2019 Marius Greuel
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+extern "C"
+{
+#include "avrdude.h"
+}
+
+#include "usb_com_helper.h"
+#include "usb_com_locator.h"
+
+class AvrdudeConsole : public UsbComLocator::IConsole
+{
+public:
+    void WriteLine(UsbComLocator::MessageLevel level, const std::string& message) override
+    {
+        switch (level)
+        {
+        case UsbComLocator::MessageLevel::Verbose:
+            avrdude_message(MSG_NOTICE, "%s: %s\n", progname, message.c_str());
+            break;
+        case UsbComLocator::MessageLevel::Debug:
+            avrdude_message(MSG_DEBUG, "%s: %s\n", progname, message.c_str());
+            break;
+        default:
+            avrdude_message(MSG_INFO, "%s: %s\n", progname, message.c_str());
+            break;
+        }
+    }
+};
+
+int win32_find_usb_com_port(const char* deviceId, char** port, bool wait_for_device, bool find_related_devices, bool auto_reset)
+{
+    try
+    {
+        UsbComLocator::Locator::Options options;
+        options.WaitForDevice = wait_for_device;
+        options.FindRelatedDevices = find_related_devices;
+        options.AutoReset = auto_reset;
+
+        AvrdudeConsole console;
+        UsbComLocator::Locator locator(&console);
+        std::string filename = locator.FindPortForDevice(deviceId, &options);
+        if (filename.empty())
+        {
+            *port = nullptr;
+            return -1;
+        }
+
+        filename = "\\\\.\\" + filename;
+
+        size_t bufferSize = filename.size() + 1;
+        *port = static_cast<char*>(malloc(bufferSize));
+        if (*port == nullptr)
+        {
+            return -1;
+        }
+
+        strncpy(*port, filename.c_str(), bufferSize);
+        return 0;
+    }
+    catch (const std::exception&)
+    {
+        *port = nullptr;
+        return -1;
+    }
+}
diff --git a/src/msvc/usb_com_helper.h b/src/msvc/usb_com_helper.h
new file mode 100644
index 00000000..9dcd19a9
--- /dev/null
+++ b/src/msvc/usb_com_helper.h
@@ -0,0 +1,35 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2019 Marius Greuel
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef USB_COM_HELPER_H
+#define USB_COM_HELPER_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int win32_find_usb_com_port(const char* deviceId, char** port, bool wait_for_device, bool find_related_devices, bool auto_reset);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // USB_COM_HELPER_H
diff --git a/src/msvc/usb_com_locator.h b/src/msvc/usb_com_locator.h
new file mode 100644
index 00000000..13323d80
--- /dev/null
+++ b/src/msvc/usb_com_locator.h
@@ -0,0 +1,1074 @@
+//
+// usb_com_locator.h
+// Copyright (C) 2019 Marius Greuel. All rights reserved.
+//
+
+#pragma once
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <initguid.h>
+#include <devpkey.h>
+#include <setupapi.h>
+#include <winioctl.h>
+#include <cassert>
+#include <cstdarg>
+#include <cwchar>
+#include <cwctype>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#pragma comment(lib, "setupapi.lib")
+
+namespace UsbComLocator
+{
+    enum class MessageLevel
+    {
+        Error,
+        Info,
+        Verbose,
+        Debug,
+    };
+
+    struct IConsole
+    {
+        virtual void WriteLine(MessageLevel level, const std::string& message) = 0;
+    };
+
+    class Win32
+    {
+    public:
+        static std::wstring CreateMessageFromHResult(long hr)
+        {
+            std::wstring message;
+
+            LPWSTR pszMessage = nullptr;
+            DWORD dwChars = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, hr, 0, (LPWSTR)&pszMessage, 0, nullptr);
+            if (dwChars > 0 && pszMessage != nullptr)
+            {
+                if (dwChars > 0 && pszMessage[dwChars - 1] == '\n')
+                    dwChars--;
+
+                if (dwChars > 0 && pszMessage[dwChars - 1] == '\r')
+                    dwChars--;
+
+                message = std::wstring(pszMessage, dwChars);
+
+                LocalFree(pszMessage);
+            }
+
+            return message;
+        }
+
+    };
+
+    class Handle
+    {
+    public:
+        Handle() = default;
+
+        ~Handle()
+        {
+            Close();
+        }
+
+        Handle(const Handle&) = delete;
+        Handle& operator=(const Handle& other) = delete;
+        Handle(Handle&&) = default;
+        Handle& operator=(Handle&& other) = default;
+
+        operator HANDLE() const noexcept
+        {
+            return m_handle;
+        }
+
+        Handle& operator=(HANDLE handle)
+        {
+            Close();
+            m_handle = handle;
+            return *this;
+        }
+
+        void Close() noexcept
+        {
+            if (m_handle != nullptr)
+            {
+                CloseHandle(m_handle);
+                m_handle = nullptr;
+            }
+        }
+
+        void Attach(HANDLE handle) noexcept
+        {
+            assert(m_handle == nullptr);
+            m_handle = handle;
+        }
+
+        HANDLE Detach() noexcept
+        {
+            HANDLE handle = m_handle;
+            m_handle = nullptr;
+            return handle;
+        }
+
+    protected:
+        HANDLE m_handle = nullptr;
+    };
+
+    class Encoding
+    {
+    public:
+        static std::string ToAnsi(const std::wstring& text)
+        {
+            std::string str;
+
+            if (!text.empty())
+            {
+                int chars = ::WideCharToMultiByte(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), nullptr, 0, nullptr, nullptr);
+                if (chars == 0)
+                {
+                    return std::string();
+                }
+
+                str.resize(chars);
+                chars = ::WideCharToMultiByte(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), &str[0], static_cast<int>(str.size()), nullptr, nullptr);
+                if (chars == 0)
+                {
+                    return std::string();
+                }
+            }
+
+            return str;
+        }
+
+        static std::wstring ToUnicode(const std::string& text)
+        {
+            std::wstring str;
+
+            if (!text.empty())
+            {
+                int chars = ::MultiByteToWideChar(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), nullptr, 0);
+                if (chars == 0)
+                {
+                    return std::wstring();
+                }
+
+                str.resize(chars);
+                chars = ::MultiByteToWideChar(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), &str[0], static_cast<int>(str.size()));
+                if (chars == 0)
+                {
+                    return std::wstring();
+                }
+            }
+
+            return str;
+        }
+    };
+
+    class RegistryKey
+    {
+    public:
+        RegistryKey() = default;
+
+        RegistryKey(HKEY hKey) : m_hKey(hKey)
+        {
+        }
+
+        ~RegistryKey()
+        {
+            Close();
+        }
+
+        RegistryKey(const RegistryKey&) = delete;
+        RegistryKey& operator=(const RegistryKey& other) = delete;
+        RegistryKey(RegistryKey&&) = default;
+        RegistryKey& operator=(RegistryKey&& other) = default;
+
+        operator HKEY() const noexcept
+        {
+            return m_hKey;
+        }
+
+        void Close() noexcept
+        {
+            if (m_hKey != nullptr)
+            {
+                RegCloseKey(m_hKey);
+                m_hKey = nullptr;
+            }
+        }
+
+        void Attach(HKEY handle) noexcept
+        {
+            assert(m_hKey == nullptr);
+            m_hKey = handle;
+        }
+
+        HKEY Detach() noexcept
+        {
+            HKEY handle = m_hKey;
+            m_hKey = nullptr;
+            return handle;
+        }
+
+        HRESULT QueryValue(LPCWSTR pszValueName, std::wstring& value) noexcept
+        {
+            DWORD dwType = 0;
+            DWORD dwRequiredSize = 0;
+            LSTATUS nError = RegQueryValueExW(m_hKey, pszValueName, nullptr, &dwType, nullptr, &dwRequiredSize);
+            if (nError != ERROR_SUCCESS)
+            {
+                return HRESULT_FROM_WIN32(nError);
+            }
+
+            if (dwType != REG_SZ && dwType != REG_EXPAND_SZ)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
+            }
+
+            std::vector<uint8_t> buffer(dwRequiredSize);
+            nError = RegQueryValueExW(m_hKey, pszValueName, nullptr, &dwType, buffer.data(), &dwRequiredSize);
+            if (nError != ERROR_SUCCESS)
+            {
+                return HRESULT_FROM_WIN32(nError);
+            }
+
+            LPCWSTR pszValue = reinterpret_cast<LPCWSTR>(buffer.data());
+            DWORD dwChars = dwRequiredSize / sizeof(WCHAR);
+            if (dwChars > 0 && pszValue[dwChars - 1] == 0)
+            {
+                dwChars--;
+            }
+
+            value = std::wstring(pszValue, dwChars);
+            return S_OK;
+        }
+
+    protected:
+        HKEY m_hKey = nullptr;
+    };
+
+    class DeviceInfo : public SP_DEVINFO_DATA
+    {
+    public:
+        DeviceInfo()
+        {
+            (SP_DEVINFO_DATA&)(*this) = { sizeof(SP_DEVINFO_DATA) };
+        }
+
+        DeviceInfo(const DeviceInfo&) = delete;
+        DeviceInfo& operator=(const DeviceInfo& other) = delete;
+        DeviceInfo(DeviceInfo&&) = delete;
+        DeviceInfo& operator=(DeviceInfo&& other) = delete;
+
+    public:
+        HRESULT Open(HDEVINFO hDeviceInfoSet, LPCWSTR pszDeviceInstanceId, HWND hwndParent = nullptr, DWORD dwOpenFlags = 0)
+        {
+            if (!SetupDiOpenDeviceInfoW(hDeviceInfoSet, pszDeviceInstanceId, hwndParent, dwOpenFlags, this))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, bool& value)
+        {
+            DEVPROP_BOOLEAN buffer{};
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), &dwRequiredSize, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (type != DEVPROP_TYPE_BOOLEAN)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            value = buffer != DEVPROP_FALSE;
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, uint32_t& value)
+        {
+            uint32_t buffer{};
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), &dwRequiredSize, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (type != DEVPROP_TYPE_UINT32)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            value = buffer;
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, GUID& value)
+        {
+            GUID buffer{};
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), &dwRequiredSize, 0))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            if (type != DEVPROP_TYPE_GUID)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            value = buffer;
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, std::wstring& value)
+        {
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, nullptr, 0, &dwRequiredSize, 0))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            if (type != DEVPROP_TYPE_STRING)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            std::vector<uint8_t> buffer(dwRequiredSize);
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, buffer.data(), dwRequiredSize, nullptr, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            LPCWSTR pszValue = reinterpret_cast<LPCWSTR>(buffer.data());
+            DWORD dwChars = dwRequiredSize / sizeof(WCHAR);
+            if (dwChars > 0 && pszValue[dwChars - 1] == 0)
+            {
+                dwChars--;
+            }
+
+            value = std::wstring(pszValue, dwChars);
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, std::vector<std::wstring>& values)
+        {
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, nullptr, 0, &dwRequiredSize, 0))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            if (type != DEVPROP_TYPE_STRING_LIST)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            std::vector<uint8_t> buffer(dwRequiredSize);
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, buffer.data(), dwRequiredSize, nullptr, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            auto data = reinterpret_cast<const wchar_t*>(buffer.data());
+            while (*data != 0)
+            {
+                values.push_back(data);
+                data += wcslen(data) + 1;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT GetRegistryValue(HDEVINFO hDeviceInfoSet, LPCWSTR pszValueName, std::wstring& value)
+        {
+            HKEY hKey = SetupDiOpenDevRegKey(hDeviceInfoSet, this, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
+            if (hKey == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            RegistryKey key(hKey);
+            return key.QueryValue(pszValueName, value);
+        }
+    };
+
+    class DeviceInfoSet
+    {
+    public:
+        DeviceInfoSet() = default;
+        DeviceInfoSet(const DeviceInfoSet&) = delete;
+        DeviceInfoSet& operator=(const DeviceInfoSet& other) = delete;
+        DeviceInfoSet(DeviceInfoSet&&) = delete;
+        DeviceInfoSet& operator=(DeviceInfoSet&& other) = delete;
+
+        ~DeviceInfoSet()
+        {
+            Destroy();
+        }
+
+    public:
+        operator HDEVINFO() const noexcept
+        {
+            return m_hDeviceInfoSet;
+        }
+
+    public:
+        HRESULT Create(const GUID* classGuid, HWND hwndParent)
+        {
+            Destroy();
+
+            HDEVINFO hDeviceInfoSet = SetupDiCreateDeviceInfoList(classGuid, hwndParent);
+            if (hDeviceInfoSet == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            m_hDeviceInfoSet = hDeviceInfoSet;
+            return S_OK;
+        }
+
+        HRESULT GetClassDevs(const GUID* classGuid, PCWSTR pszEnumerator, HWND hWndParent, DWORD dwFlags)
+        {
+            Destroy();
+
+            HDEVINFO hDeviceInfoSet = SetupDiGetClassDevsW(classGuid, pszEnumerator, hWndParent, dwFlags);
+            if (hDeviceInfoSet == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            m_hDeviceInfoSet = hDeviceInfoSet;
+            return S_OK;
+        }
+
+        void Destroy()
+        {
+            if (m_hDeviceInfoSet != INVALID_HANDLE_VALUE)
+            {
+                SetupDiDestroyDeviceInfoList(m_hDeviceInfoSet);
+                m_hDeviceInfoSet = INVALID_HANDLE_VALUE;
+            }
+        }
+
+    private:
+        HDEVINFO m_hDeviceInfoSet = INVALID_HANDLE_VALUE;
+    };
+
+    class Device
+    {
+    public:
+        Device() = default;
+        Device(const Device&) = delete;
+        Device& operator=(const Device& other) = delete;
+        Device(Device&&) = delete;
+        Device& operator=(Device&& other) = delete;
+
+    public:
+        bool IsBootloaderMode() const
+        {
+            return !m_isCompositeDevice;
+        }
+
+        HRESULT Open(
+            DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE,
+            DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
+            LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr)
+        {
+            HANDLE hDevice = CreateFileW(
+                (L"\\\\.\\" + m_port).c_str(),
+                dwDesiredAccess,
+                0,
+                lpSecurityAttributes,
+                OPEN_EXISTING,
+                dwFlagsAndAttributes,
+                nullptr);
+            if (hDevice == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            m_hDevice = hDevice;
+            return S_OK;
+        }
+
+        void Close()
+        {
+            m_hDevice.Close();
+        }
+
+        HRESULT WaitForPortAvailability(uint32_t retries = 100, uint32_t timeout = 10)
+        {
+            while (true)
+            {
+                HRESULT hr = Open();
+                if (SUCCEEDED(hr))
+                {
+                    Close();
+                    return S_OK;
+                }
+
+                if (retries == 0)
+                {
+                    return hr;
+                }
+
+                Sleep(timeout);
+                retries--;
+            }
+        }
+
+        HRESULT Reset()
+        {
+            HRESULT hr = Open();
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            EscapeCommFunction(m_hDevice, CLRDTR);
+
+            DCB dcb = { sizeof(DCB) };
+            dcb.BaudRate = CBR_1200;
+            dcb.fDtrControl = DTR_CONTROL_DISABLE;
+            dcb.fRtsControl = RTS_CONTROL_DISABLE;
+            dcb.ByteSize = 8;
+            dcb.Parity = NOPARITY;
+            dcb.StopBits = ONESTOPBIT;
+            SetCommState(m_hDevice, &dcb);
+
+            Close();
+            return S_OK;
+        }
+
+    public:
+        std::wstring m_instanceId;
+        std::wstring m_port;
+        std::wstring m_location;
+        std::wstring m_productName;
+        bool m_isPresent = false;
+        bool m_isCompositeDevice = false;
+        uint16_t m_vid = 0;
+        uint16_t m_pid = 0;
+        Handle m_hDevice;
+    };
+
+    class Enumerator
+    {
+    public:
+        Enumerator() = default;
+        Enumerator(const Enumerator&) = delete;
+        Enumerator& operator=(const Enumerator& other) = delete;
+        Enumerator(Enumerator&&) = delete;
+        Enumerator& operator=(Enumerator&& other) = delete;
+
+    public:
+        std::vector<std::shared_ptr<Device>>& GetDevices() { return m_devices; }
+
+    public:
+        HRESULT EnumerateDevices(bool includeUnpluggedDevices = false)
+        {
+            m_devices.clear();
+
+            DeviceInfoSet deviceInfoSet;
+            HRESULT hr = deviceInfoSet.GetClassDevs(&GUID_DEVINTERFACE_COMPORT, nullptr, nullptr, (includeUnpluggedDevices ? 0 : DIGCF_PRESENT) | DIGCF_DEVICEINTERFACE);
+            if (FAILED(hr))
+                return hr;
+
+            for (DWORD index = 0; ; index++)
+            {
+                DeviceInfo deviceInfo;
+                if (!SetupDiEnumDeviceInfo(deviceInfoSet, index, &deviceInfo))
+                {
+                    DWORD dwError = GetLastError();
+                    if (dwError != ERROR_NO_MORE_ITEMS)
+                    {
+                        return HRESULT_FROM_WIN32(dwError);
+                    }
+
+                    break;
+                }
+
+                auto device = std::make_shared<Device>();
+                if (SUCCEEDED(deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_InstanceId, device->m_instanceId)))
+                {
+                    ParseVidPid(device->m_instanceId.c_str(), L"VID_", device->m_vid);
+                    ParseVidPid(device->m_instanceId.c_str(), L"PID_", device->m_pid);
+
+                    deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_IsPresent, device->m_isPresent);
+                    deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_BusReportedDeviceDesc, device->m_productName);
+                    deviceInfo.GetRegistryValue(deviceInfoSet, L"PortName", device->m_port);
+
+                    device->m_isCompositeDevice = IsCompositeDevice(device->m_instanceId.c_str());
+                    if (device->m_isCompositeDevice)
+                    {
+                        GetCompositeDevicePortLocation(deviceInfoSet, deviceInfo, device->m_location);
+                    }
+                    else
+                    {
+                        deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_LocationInfo, device->m_location);
+                    }
+
+                    m_devices.push_back(std::move(device));
+                }
+            }
+
+            return S_OK;
+        }
+
+        bool ParseVidPid(_In_z_ wchar_t const* buffer, wchar_t const* prefix, uint16_t& value)
+        {
+            wchar_t const* match = wcsstr(buffer, prefix);
+            if (match == nullptr)
+            {
+                return false;
+            }
+
+            match += wcslen(prefix);
+
+            wchar_t* endptr = nullptr;
+            value = static_cast<uint16_t>(std::wcstoul(match, &endptr, 16));
+            return endptr == match + 4;
+        }
+
+    private:
+        HRESULT GetCompositeDevicePortLocation(HDEVINFO hDeviceInfoSet, DeviceInfo& deviceInfo, std::wstring& location)
+        {
+            DeviceInfoSet deviceInfoSet;
+            HRESULT hr = deviceInfoSet.Create(nullptr, nullptr);
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            std::wstring parentDeviceInstanceId;
+            hr = deviceInfo.GetProperty(hDeviceInfoSet, &DEVPKEY_Device_Parent, parentDeviceInstanceId);
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            DeviceInfo parentDeviceInfo;
+            hr = parentDeviceInfo.Open(deviceInfoSet, parentDeviceInstanceId.c_str());
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            hr = parentDeviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_LocationInfo, location);
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            return S_OK;
+        }
+
+        static bool IsCompositeDevice(LPCWSTR deviceInstanceId)
+        {
+            return wcsstr(deviceInstanceId, L"&MI_") != nullptr;
+        }
+
+    private:
+        std::vector<std::shared_ptr<Device>> m_devices;
+    };
+
+    class Locator
+    {
+    public:
+        struct Options
+        {
+            bool WaitForDevice = true;
+            bool FindRelatedDevices = true;
+            bool AutoReset = true;
+            uint32_t Retries = 0xFFFFFFFF;
+            uint32_t RetryIntervall = 100;
+        };
+
+    public:
+        Locator(IConsole* console = nullptr) : m_console(console)
+        {
+        }
+
+        Locator(const Locator&) = delete;
+        Locator& operator=(const Locator& other) = delete;
+        Locator(Locator&&) = delete;
+        Locator& operator=(Locator&& other) = delete;
+
+    public:
+        std::string FindPortForDevice(const std::string& deviceId, const Options* options = nullptr)
+        {
+            auto device = FindDevice(Encoding::ToUnicode(deviceId), options);
+            if (device == nullptr)
+                return std::string();
+
+            return Encoding::ToAnsi(device->m_port);
+        }
+
+        std::wstring FindPortForDevice(const std::wstring& deviceId, const Options* options = nullptr)
+        {
+            auto device = FindDevice(deviceId, options);
+            if (device == nullptr)
+                return std::wstring();
+
+            return device->m_port;
+        }
+
+        std::shared_ptr<Device> FindDevice(const std::string& deviceId, const Options* options = nullptr)
+        {
+            return FindDevice(Encoding::ToUnicode(deviceId), options);
+        }
+
+        std::shared_ptr<Device> FindDevice(const std::wstring& deviceId, const Options* options = nullptr)
+        {
+            if (options == nullptr)
+            {
+                static Options defaults;
+                options = &defaults;
+            }
+
+            uint32_t retries = options->Retries;
+            while (true)
+            {
+                m_enumerator.EnumerateDevices(true);
+
+                auto devices = FindDevices(deviceId, options->FindRelatedDevices);
+                if (devices.size() == 0)
+                {
+                    throw std::runtime_error("Device not found.");
+                }
+
+                auto device = devices[0];
+                if (device->m_isPresent)
+                {
+                    PrintMessage(MessageLevel::Verbose, "Found device at port '%ws'", device->m_port.c_str());
+                    PrintDeviceProperties(device);
+
+                    HRESULT hr = device->WaitForPortAvailability(options->Retries, options->RetryIntervall);
+                    if (FAILED(hr))
+                    {
+                        PrintMessage(MessageLevel::Error, "Failed to open serial port: %ws", Win32::CreateMessageFromHResult(hr).c_str());
+                        throw std::runtime_error("Failed to open serial port.");
+                    }
+
+                    if (device->IsBootloaderMode() || !options->AutoReset)
+                    {
+                        return device;
+                    }
+
+                    PrintMessage(MessageLevel::Info, "Entering bootloader mode...");
+                    hr = device->Reset();
+                    if (FAILED(hr))
+                    {
+                        PrintMessage(MessageLevel::Error, "Failed to reset device: %ws", Win32::CreateMessageFromHResult(hr).c_str());
+                        throw std::runtime_error("Failed to reset device.");
+                    }
+
+                    retries = options->Retries;
+
+                    while (true)
+                    {
+                        m_enumerator.EnumerateDevices(true);
+                        auto relatedDevices = FindRelatedDevices(devices);
+                        if (relatedDevices.size() > 0)
+                        {
+                            auto relatedDevice = relatedDevices[0];
+                            if (relatedDevice->m_isPresent)
+                            {
+                                PrintMessage(MessageLevel::Verbose, "Found device at port '%ws'", relatedDevice->m_port.c_str());
+                                PrintDeviceProperties(relatedDevice);
+
+                                hr = relatedDevice->WaitForPortAvailability(options->Retries, options->RetryIntervall);
+                                if (FAILED(hr))
+                                {
+                                    PrintMessage(MessageLevel::Error, "Failed to open serial port: %ws", Win32::CreateMessageFromHResult(hr).c_str());
+                                    throw std::runtime_error("Failed to open serial port.");
+                                }
+
+                                return relatedDevice;
+                            }
+                        }
+
+                        if (!options->WaitForDevice || retries == 0)
+                        {
+                            return nullptr;
+                        }
+
+                        Sleep(options->RetryIntervall);
+                        retries--;
+                    }
+                }
+                
+                if (!options->WaitForDevice || retries == 0)
+                {
+                    return nullptr;
+                }
+
+                if (retries == options->Retries)
+                {
+                    PrintMessage(MessageLevel::Info, "No device present, retrying every %ums...", options->RetryIntervall);
+                }
+                
+                Sleep(options->RetryIntervall);
+                retries--;
+            }
+        }
+
+        void LocateAllDevices()
+        {
+            PrintMessage(MessageLevel::Verbose, "Enumerating devices...");
+            m_enumerator.EnumerateDevices(true);
+
+            for (auto& device : m_enumerator.GetDevices())
+            {
+                PrintMessage(MessageLevel::Info, "Found device: %ws", device->m_instanceId.c_str());
+                PrintDeviceProperties(device);
+            }
+        }
+
+    private:
+        std::vector<std::shared_ptr<Device>> FindDevices(const std::wstring& deviceId, bool findRelatedDevices = false)
+        {
+            uint32_t comPort = 0;
+            uint16_t vid = 0;
+            uint16_t pid = 0;
+            if (swscanf_s(ToUpper(deviceId).c_str(), L"COM%u", &comPort) == 1)
+            {
+                auto devices = FindDevicesByPort(deviceId);
+                if (devices.size() > 0)
+                {
+                    // 1) Return plugged-in devices with exact COM port match.
+                    return devices;
+                }
+
+                auto unpluggedDevices = FindDevicesByPort(deviceId, true);
+
+                if (findRelatedDevices)
+                {
+                    auto relatedDevices = FindRelatedDevices(unpluggedDevices);
+                    if (relatedDevices.size() > 0)
+                    {
+                        // 2) Return plugged-in devices related to the COM port.
+                        return relatedDevices;
+                    }
+                }
+
+                // 3) Return unplugged devices with exact COM port match.
+                return unpluggedDevices;
+            }
+            else if (swscanf_s(ToUpper(deviceId).c_str(), L"USB:%hX:%hX", &vid, &pid) == 2)
+            {
+                auto devices = FindDevicesByUsbId(vid, pid);
+                if (devices.size() > 0)
+                {
+                    // 1) Return plugged-in devices with exact USB VID:PID match.
+                    return devices;
+                }
+
+                auto unpluggedDevices = FindDevicesByUsbId(vid, pid, std::wstring(), true);
+
+                if (findRelatedDevices)
+                {
+                    // 2) Return plugged-in devices with related USB VID:PID and HUB location.
+                    auto relatedDevices = FindRelatedDevices(unpluggedDevices);
+                    if (relatedDevices.size() > 0)
+                    {
+                        return relatedDevices;
+                    }
+
+                    // 3) Return plugged-in devices with related USB VID:PID (bootloader device never plugged-in).
+                    relatedDevices = FindRelatedDevices(vid, pid);
+                    if (relatedDevices.size() > 0)
+                    {
+                        return relatedDevices;
+                    }
+                }
+
+                // 4) Return unplugged devices with exact USB VID:PID match.
+                return unpluggedDevices;
+            }
+            else
+            {
+                throw std::runtime_error("Invalid device specifier.");
+            }
+        }
+
+        std::vector<std::shared_ptr<Device>> FindDevicesByPort(const std::wstring& port, bool includeUnpluggedDevices = false)
+        {
+            std::vector<std::shared_ptr<Device>> devices;
+
+            for (auto& device : m_enumerator.GetDevices())
+            {
+                if ((device->m_isPresent || includeUnpluggedDevices) &&
+                    _wcsicmp(device->m_port.c_str(), port.c_str()) == 0)
+                {
+                    devices.push_back(device);
+                }
+            }
+
+            return devices;
+        }
+
+        std::vector<std::shared_ptr<Device>> FindDevicesByUsbId(uint16_t vid, uint16_t pid, const std::wstring& location = std::wstring(), bool includeUnpluggedDevices = false)
+        {
+            std::vector<std::shared_ptr<Device>> devices;
+
+            for (auto& device : m_enumerator.GetDevices())
+            {
+                if ((device->m_isPresent || includeUnpluggedDevices) &&
+                    device->m_vid == vid &&
+                    device->m_pid == pid &&
+                    (location.empty() || device->m_location == location))
+                {
+                    devices.push_back(device);
+                }
+            }
+
+            return devices;
+        }
+
+        std::vector<std::shared_ptr<Device>> FindRelatedDevices(const std::vector<std::shared_ptr<Device>>& devices)
+        {
+            std::vector<std::shared_ptr<Device>> relatedDevices;
+
+            for (const auto& device : devices)
+            {
+                auto relatedPids = GetRelatedPids(device->m_pid, device->IsBootloaderMode());
+                for (auto relatedPid : relatedPids)
+                {
+                    auto list = FindDevicesByUsbId(device->m_vid, relatedPid, device->m_location);
+                    relatedDevices.insert(relatedDevices.end(), list.begin(), list.end());
+                }
+            }
+
+            return relatedDevices;
+        }
+
+        std::vector<std::shared_ptr<Device>> FindRelatedDevices(uint16_t vid, uint16_t pid)
+        {
+            std::vector<std::shared_ptr<Device>> relatedDevices;
+
+            auto relatedPids = GetRelatedPids(pid, true);
+            for (auto relatedPid : relatedPids)
+            {
+                auto list = FindDevicesByUsbId(vid, relatedPid);
+                relatedDevices.insert(relatedDevices.end(), list.begin(), list.end());
+            }
+
+            return relatedDevices;
+        }
+
+        static std::vector<uint16_t> GetRelatedPids(uint16_t pid, bool isBootloaderMode)
+        {
+            // Arduino boards with a direct USB connection (such as Leonardo or Micro)
+            // have one USB serial port for the bootloader and one for the sketch.
+            // Bootloader and sketch devices have the same VID but different PIDs.
+            // To find related PIDs (bootloader vs. sketch device), we use the following heuristics:
+            // Arduino, Adafruit, etc.:
+            // - Bootloader PID: PIDxxxx with the MSB cleared
+            // - Sketch PID: PIDxxxx with the MSB set
+            // Others, who did not understand the above (Sparkfun):
+            // - Bootloader PID: PIDxxxx
+            // - Sketch PID: PIDxxxx + 1
+
+            std::vector<uint16_t> relatedPids;
+
+            if (isBootloaderMode)
+            {
+                relatedPids.push_back(pid | 0x8000);
+                relatedPids.push_back(pid + 1);
+            }
+            else
+            {
+                relatedPids.push_back(pid & 0x7FFF);
+                relatedPids.push_back(pid - 1);
+            }
+
+            return relatedPids;
+        }
+
+    private:
+        static std::wstring ToUpper(std::wstring text)
+        {
+            for (auto& ch : text)
+            {
+                ch = static_cast<wchar_t>(std::towupper(ch));
+            }
+
+            return text;
+        }
+
+        void PrintMessage(MessageLevel level, _In_z_ _Printf_format_string_ const char* format, ...)
+        {
+            if (m_console != nullptr)
+            {
+                va_list args;
+                va_start(args, format);
+                PrintMessage(level, format, args);
+                va_end(args);
+            }
+        }
+
+        void PrintMessage(MessageLevel level, _In_z_ _Printf_format_string_ const char* format, va_list args)
+        {
+            if (m_console != nullptr)
+            {
+                va_list args2;
+                va_copy(args2, args);
+                size_t needed = vsnprintf(nullptr, 0, format, args2);
+                va_end(args2);
+
+                std::string str;
+                if (needed > 0)
+                {
+                    str.resize(needed + 1);
+                    size_t used = vsnprintf(&str[0], needed + 1, format, args);
+                    str.resize(used);
+                }
+
+                m_console->WriteLine(level, str);
+            }
+        }
+
+        void PrintDeviceProperties(const std::shared_ptr<Device>& device)
+        {
+            if (m_console != nullptr)
+            {
+                PrintMessage(MessageLevel::Debug, "- InstanceId: %ws", device->m_instanceId.c_str());
+                PrintMessage(MessageLevel::Debug, "- Port: %ws", device->m_port.c_str());
+                PrintMessage(MessageLevel::Debug, "- Location: %ws", device->m_location.c_str());
+                PrintMessage(MessageLevel::Debug, "- ProductName: %ws", device->m_productName.c_str());
+                PrintMessage(MessageLevel::Debug, "- IsPresent: %s", device->m_isPresent ? "true" : "false");
+                PrintMessage(MessageLevel::Debug, "- IsCompositeDevice: %s", device->m_isCompositeDevice ? "true" : "false");
+            }
+        }
+
+    private:
+        IConsole* m_console = nullptr;
+        Enumerator m_enumerator;
+    };
+}
diff --git a/src/ser_win32.c b/src/ser_win32.c
index fa4d7aba..26427a9c 100644
--- a/src/ser_win32.c
+++ b/src/ser_win32.c
@@ -36,6 +36,10 @@
 #include "avrdude.h"
 #include "libavrdude.h"
 
+#ifdef _MSC_VER
+#include "msvc/usb_com_helper.h"
+#endif
+
 long serial_recv_timeout = 5000; /* ms */
 
 #define W32SERBUFSIZE 1024
@@ -257,6 +261,13 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp)
 		return net_open(port + strlen("net:"), fdp);
 	}
 
+#ifdef _MSC_VER
+	if (win32_find_usb_com_port(port, &newname, true, true, true) >= 0)
+	{
+		port = newname;
+	}
+#endif
+
 	if (strncasecmp(port, "com", strlen("com")) == 0) {
 
 	    // prepend "\\\\.\\" to name, required for port # >= 10