diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp
index fe3adfd7f..865884c6f 100644
--- a/src/slic3r/Config/Version.cpp
+++ b/src/slic3r/Config/Version.cpp
@@ -192,6 +192,7 @@ size_t Index::load(const boost::filesystem::path &path)
 {
 	m_configs.clear();
 	m_vendor = path.stem().string();
+	m_path = path;
 
     boost::nowide::ifstream ifs(path.string());
     std::string line;
diff --git a/src/slic3r/Config/Version.hpp b/src/slic3r/Config/Version.hpp
index e689286af..560bc29c2 100644
--- a/src/slic3r/Config/Version.hpp
+++ b/src/slic3r/Config/Version.hpp
@@ -72,6 +72,9 @@ public:
 	// if the index is valid.
 	const_iterator				recommended() const;
 
+	// Returns the filesystem path from which this index has originally been loaded
+	const boost::filesystem::path& path() const { return m_path; }
+
 	// Load all vendor specific indices.
 	// Throws Slic3r::file_parser_error and the standard std file access exceptions.
 	static std::vector<Index>	load_db();
@@ -79,6 +82,7 @@ public:
 private:
 	std::string 				m_vendor;
 	std::vector<Version>		m_configs;
+	boost::filesystem::path		m_path;
 };
 
 } // namespace Config
diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp
index b400e27ea..15a09aa71 100644
--- a/src/slic3r/GUI/FirmwareDialog.cpp
+++ b/src/slic3r/GUI/FirmwareDialog.cpp
@@ -5,11 +5,14 @@
 #include <stdexcept>
 #include <boost/format.hpp>
 #include <boost/asio.hpp>
-#include <boost/filesystem/path.hpp>
-#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem.hpp>
 #include <boost/log/trivial.hpp>
 #include <boost/optional.hpp>
 
+#if _WIN32
+	#include <regex>
+#endif
+
 #include "libslic3r/Utils.hpp"
 #include "avrdude/avrdude-slic3r.hpp"
 #include "GUI.hpp"
@@ -104,7 +107,7 @@ struct FirmwareDialog::priv
 
 	// GUI elements
 	wxComboBox *port_picker;
-	wxStaticText *port_autodetect;
+	wxStaticText *txt_port_autodetect;
 	wxFilePickerCtrl *hex_picker;
 	wxStaticText *txt_status;
 	wxGauge *progressbar;
@@ -131,6 +134,7 @@ struct FirmwareDialog::priv
 	// Data
 	std::vector<SerialPortInfo> ports;
 	optional<SerialPortInfo> port;
+	bool port_autodetect;
 	HexFile hex_file;
 
 	// This is a shared pointer holding the background AvrDude task
@@ -147,6 +151,7 @@ struct FirmwareDialog::priv
 		btn_flash_label_flashing(_(L("Cancel"))),
 		label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))),
 		timer_pulse(q),
+		port_autodetect(false),
 		progress_tasks_done(0),
 		progress_tasks_bar(0),
 		user_cancelled(false),
@@ -158,7 +163,8 @@ struct FirmwareDialog::priv
 	void set_txt_status(const wxString &label);
 	void flashing_start(unsigned tasks);
 	void flashing_done(AvrDudeComplete complete);
-	void enable_port_picker(bool enable);
+	void set_autodetect(bool autodetect);
+	void update_flash_enabled();
 	void load_hex_file(const wxString &path);
 	void queue_event(AvrdudeEvent aevt, wxString message);
 
@@ -171,6 +177,7 @@ struct FirmwareDialog::priv
 	void prepare_mk2();
 	void prepare_mk3();
 	void prepare_avr109(Avr109Pid usb_pid);
+	bool get_serial_port();
 	void perform_upload();
 
 	void user_cancel();
@@ -211,8 +218,10 @@ void FirmwareDialog::priv::find_serial_ports()
 					idx = i;
 					break;
 				}
-			if (idx != -1)
+			if (idx != -1) {
 				port_picker->SetSelection(idx);
+				update_flash_enabled();
+			}
 		}
 	}
 }
@@ -277,20 +286,30 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
 	}
 }
 
-void FirmwareDialog::priv::enable_port_picker(bool enable)
+void FirmwareDialog::priv::set_autodetect(bool autodetect)
 {
-	port_picker->Show(enable);
-	btn_rescan->Show(enable);
-	port_autodetect->Show(! enable);
+	port_autodetect = autodetect;
+
+	port_picker->Show(!autodetect);
+	btn_rescan->Show(!autodetect);
+	txt_port_autodetect->Show(autodetect);
 	q->Layout();
 	fit_no_shrink();
 }
 
+void FirmwareDialog::priv::update_flash_enabled()
+{
+	const bool hex_exists = wxFileExists(hex_picker->GetPath());
+	const bool port_valid = port_autodetect || get_serial_port();
+
+	btn_flash->Enable(hex_exists && port_valid);
+}
+
 void FirmwareDialog::priv::load_hex_file(const wxString &path)
 {
 	hex_file = HexFile(path.wx_str());
-	const bool auto_lookup = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
-	enable_port_picker(! auto_lookup);
+	const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
+	set_autodetect(autodetect);
 }
 
 void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message)
@@ -553,6 +572,31 @@ void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid)
 }
 
 
+bool FirmwareDialog::priv::get_serial_port()
+{
+	const int selection = port_picker->GetSelection();
+	if (selection != wxNOT_FOUND) {
+		port = this->ports[selection];
+	} else {
+		// User has supplied a custom filename
+
+		std::string path_u8 = GUI::into_u8(port_picker->GetValue());
+#ifdef _WIN32
+		static const std::regex com_pattern("COM[0-9]+", std::regex::icase);
+		std::smatch matches;
+		if (std::regex_match(path_u8, matches, com_pattern)) {
+#else
+		if (fs::is_other(fs::path(path_u8))) {
+#endif
+			port = SerialPortInfo(std::move(path_u8));
+		} else {
+			port = boost::none;
+		}
+	}
+
+	return !!port;
+}
+
 void FirmwareDialog::priv::perform_upload()
 {
 	auto filename = hex_picker->GetPath();
@@ -560,14 +604,8 @@ void FirmwareDialog::priv::perform_upload()
 
 	load_hex_file(filename);  // Might already be loaded, but we want to make sure it's fresh
 
-	int selection = port_picker->GetSelection();
-	if (selection != wxNOT_FOUND) {
-		port = this->ports[selection];
-
-		// Verify whether the combo box list selection equals to the combo box edit value.
-		if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
-			return;
-		}
+	if (!port_autodetect && !get_serial_port()) {
+		return;
 	}
 
 	const bool extra_verbose = false;   // For debugging
@@ -769,13 +807,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
 
 	auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
 	p->port_picker = new wxComboBox(panel, wxID_ANY);
-	p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
+	p->txt_port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
 	p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
 	auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
 	port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
 	port_sizer->Add(p->btn_rescan, 0);
-	port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
-	p->enable_port_picker(true);
+	port_sizer->Add(p->txt_port_autodetect, 1, wxEXPAND);
+	p->set_autodetect(false);
 
 	auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
 	p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
@@ -836,10 +874,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
 	p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
 		if (wxFileExists(evt.GetPath())) {
 			this->p->load_hex_file(evt.GetPath());
-			this->p->btn_flash->Enable();
 		}
+		p->update_flash_enabled();
 	});
 
+	p->port_picker->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { p->update_flash_enabled(); });
+	p->port_picker->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { p->update_flash_enabled(); });
+
 	p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [=](wxCollapsiblePaneEvent &evt) {
 		if (evt.GetCollapsed()) {
 			this->SetMinSize(wxSize(p->min_width, p->min_height));
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 3aada45f3..4f1c3adc8 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -265,10 +265,8 @@ bool GUI_App::on_init_inner()
             }
 
             CallAfter([this] {
-                if (!config_wizard_startup(app_conf_exists)) {
-                    // Only notify if there was no wizard so as not to bother too much ...
-                    preset_updater->slic3r_update_notify();
-                }
+                config_wizard_startup(app_conf_exists);
+                preset_updater->slic3r_update_notify();
                 preset_updater->sync(preset_bundle);
             });
         }
diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp
index 7d624af90..413c6ffee 100644
--- a/src/slic3r/GUI/ProgressStatusBar.hpp
+++ b/src/slic3r/GUI/ProgressStatusBar.hpp
@@ -2,6 +2,7 @@
 #define PROGRESSSTATUSBAR_HPP
 
 #include <memory>
+#include <string>
 #include <functional>
 #include <string>
 
diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp
index f34cd8db1..8c3ced31a 100644
--- a/src/slic3r/Utils/PresetUpdater.cpp
+++ b/src/slic3r/Utils/PresetUpdater.cpp
@@ -4,6 +4,7 @@
 #include <thread>
 #include <unordered_map>
 #include <ostream>
+#include <utility>
 #include <stdexcept>
 #include <boost/format.hpp>
 #include <boost/algorithm/string.hpp>
@@ -45,10 +46,25 @@ static const char *INDEX_FILENAME = "index.idx";
 static const char *TMP_EXTENSION = ".download";
 
 
+void copy_file_fix(const fs::path &source, const fs::path &target)
+{
+	static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;   // aka 644
+
+	BOOST_LOG_TRIVIAL(debug) << boost::format("PresetUpdater: Copying %1% -> %2%") % source % target;
+
+	// Make sure the file has correct permission both before and after we copy over it
+	if (fs::exists(target)) {
+		fs::permissions(target, perms);
+	}
+	fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
+	fs::permissions(target, perms);
+}
+
 struct Update
 {
 	fs::path source;
 	fs::path target;
+
 	Version version;
 	std::string vendor;
 	std::string changelog_url;
@@ -61,7 +77,13 @@ struct Update
 		, changelog_url(std::move(changelog_url))
 	{}
 
-	friend std::ostream& operator<<(std::ostream& os , const Update &self) {
+	void install() const
+	{
+		copy_file_fix(source, target);
+	}
+
+	friend std::ostream& operator<<(std::ostream& os, const Update &self)
+	{
 		os << "Update(" << self.source.string() << " -> " << self.target.string() << ')';
 		return os;
 	}
@@ -115,7 +137,6 @@ struct PresetUpdater::priv
 	bool enabled_version_check;
 	bool enabled_config_update;
 	std::string version_check_url;
-	bool had_config_update;
 
 	fs::path cache_path;
 	fs::path rsrc_path;
@@ -135,13 +156,10 @@ struct PresetUpdater::priv
 	void check_install_indices() const;
 	Updates get_config_updates() const;
 	void perform_updates(Updates &&updates, bool snapshot = true) const;
-
-	static void copy_file(const fs::path &from, const fs::path &to);
 };
 
 PresetUpdater::priv::priv()
 	: ver_slic3r(get_slic3r_version())
-	, had_config_update(false)
 	, cache_path(fs::path(Slic3r::data_dir()) / "cache")
 	, rsrc_path(fs::path(resources_dir()) / "profiles")
 	, vendor_path(fs::path(Slic3r::data_dir()) / "vendor")
@@ -273,7 +291,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
 			try {
 				new_index.load(idx_path_temp);
 			} catch (const std::exception & /* err */) {
-				BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading a downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name;
+				BOOST_LOG_TRIVIAL(error) << boost::format("Could not load downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name;
 				continue;
 			}
 			if (new_index.version() < index.version()) {
@@ -323,7 +341,7 @@ void PresetUpdater::priv::check_install_indices() const
 
 			if (! fs::exists(path_in_cache)) {
 				BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename();
-				copy_file(path, path_in_cache);
+				copy_file_fix(path, path_in_cache);
 			} else {
 				Index idx_rsrc, idx_cache;
 				idx_rsrc.load(path);
@@ -331,7 +349,7 @@ void PresetUpdater::priv::check_install_indices() const
 
 				if (idx_cache.version() < idx_rsrc.version()) {
 					BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename();
-					copy_file(path, path_in_cache);
+					copy_file_fix(path, path_in_cache);
 				}
 			}
 		}
@@ -346,6 +364,7 @@ Updates PresetUpdater::priv::get_config_updates() const
 
 	for (const auto idx : index_db) {
 		auto bundle_path = vendor_path / (idx.vendor() + ".ini");
+		auto bundle_path_idx = vendor_path / idx.path().filename();
 
 		if (! fs::exists(bundle_path)) {
 			BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
@@ -360,8 +379,31 @@ Updates PresetUpdater::priv::get_config_updates() const
 		const auto recommended = idx.recommended();
 		if (recommended == idx.end()) {
 			BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
+			// XXX: what should be done here?
+			continue;
 		}
 
+		// Load 'installed' idx, if any.
+		// 'Installed' indices are kept alongside the bundle in the `vendor` subdir
+		// for bookkeeping to remember a cancelled update and not offer it again.
+		if (fs::exists(bundle_path_idx)) {
+			Index existing_idx;
+			try {
+				existing_idx.load(bundle_path_idx);
+
+				const auto existing_recommended = existing_idx.recommended();
+				if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) {
+					// The user has already seen (and presumably rejected) this update
+					BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor();
+					continue;
+				}
+			} catch (const std::exception & /* err */) {
+				BOOST_LOG_TRIVIAL(error) << boost::format("Could nto load installed index %1%") % bundle_path_idx;
+			}
+		}
+
+		copy_file_fix(idx.path(), bundle_path_idx);
+
 		const auto ver_current = idx.find(vp.config_version);
 		const bool ver_current_found = ver_current != idx.end();
 		if (! ver_current_found) {
@@ -453,10 +495,10 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
 		for (const auto &update : updates.updates) {
 			BOOST_LOG_TRIVIAL(info) << '\t' << update;
 
-			copy_file(update.source, update.target);
+			update.install();
 
 			PresetBundle bundle;
-			bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
+			bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
 
 			BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets")
 				% (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
@@ -491,19 +533,6 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
 	}
 }
 
-void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &target)
-{
-	static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;   // aka 644
-
-	// Make sure the file has correct permission both before and after we copy over it
-	if (fs::exists(target)) {
-		fs::permissions(target, perms);
-	}
-	fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
-	fs::permissions(target, perms);
-}
-
-
 PresetUpdater::PresetUpdater() :
 	p(new priv())
 {}
@@ -542,11 +571,6 @@ void PresetUpdater::slic3r_update_notify()
 {
 	if (! p->enabled_version_check) { return; }
 
-	if (p->had_config_update) {
-		BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed";
-		return;
-	}
-
 	auto* app_config = GUI::wxGetApp().app_config;
 	const auto ver_online_str = app_config->get("version_online");
 	const auto ver_online = Semver::parse(ver_online_str);
@@ -594,8 +618,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
 			incompats_map.emplace(std::make_pair(incompat.vendor, std::move(restrictions)));
 		}
 
-		p->had_config_update = true;   // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl
-
 		GUI::MsgDataIncompatible dlg(std::move(incompats_map));
 		const auto res = dlg.ShowModal();
 		if (res == wxID_REPLACE) {
@@ -620,8 +642,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
 			updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
 		}
 
-		p->had_config_update = true;   // Ditto, see above
-
 		GUI::MsgUpdateConfig dlg(updates_msg);
 
 		const auto res = dlg.ShowModal();
@@ -631,7 +651,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
 
 			// Reload global configuration
 			auto *app_config = GUI::wxGetApp().app_config;
-            GUI::wxGetApp().preset_bundle->load_presets(*app_config);
+			GUI::wxGetApp().preset_bundle->load_presets(*app_config);
 			GUI::wxGetApp().load_current_presets();
 			return R_UPDATE_INSTALLED;
 		} else {
diff --git a/src/slic3r/Utils/Semver.hpp b/src/slic3r/Utils/Semver.hpp
index 2fb4e3f4b..a755becaa 100644
--- a/src/slic3r/Utils/Semver.hpp
+++ b/src/slic3r/Utils/Semver.hpp
@@ -137,6 +137,11 @@ public:
 	Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; }
 	Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; }
 
+	// Stream output
+	friend std::ostream& operator<<(std::ostream& os, const Semver &self) {
+		os << self.to_string();
+		return os;
+	}
 private:
 	semver_t ver;
 
diff --git a/src/slic3r/Utils/Serial.hpp b/src/slic3r/Utils/Serial.hpp
index e4a28de09..67d64b4ec 100644
--- a/src/slic3r/Utils/Serial.hpp
+++ b/src/slic3r/Utils/Serial.hpp
@@ -17,6 +17,9 @@ struct SerialPortInfo {
 	std::string friendly_name;
 	bool        is_printer = false;
 
+	SerialPortInfo() {}
+	SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {}
+
 	bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
 };