diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index 073c04736..83d642910 100644
--- a/data/locale/en-US.ini
+++ b/data/locale/en-US.ini
@@ -962,6 +962,12 @@ AdvSceneSwitcher.action.queue.type.start="Start"
 AdvSceneSwitcher.action.queue.type.stop="Stop"
 AdvSceneSwitcher.action.queue.entry.add="{{actions}}actions of macro{{macros}}to queue{{queues}}"
 AdvSceneSwitcher.action.queue.entry.other="{{actions}}queue{{queues}}"
+AdvSceneSwitcher.action.window="Window"
+AdvSceneSwitcher.action.window.type.setFocusWindow="Set focus window to"
+AdvSceneSwitcher.action.window.type.maximizeWindow="Maximize window"
+AdvSceneSwitcher.action.window.type.minimizeWindow="Minimize window"
+AdvSceneSwitcher.action.window.type.closeWindow="Close window"
+AdvSceneSwitcher.action.window.entry="{{actions}}{{windows}}{{regex}}"
 
 ; Hotkey
 AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
diff --git a/plugins/base/CMakeLists.txt b/plugins/base/CMakeLists.txt
index 29e8fff1c..ba2f4ee98 100644
--- a/plugins/base/CMakeLists.txt
+++ b/plugins/base/CMakeLists.txt
@@ -69,6 +69,8 @@ target_sources(
           macro-action-wait.hpp
           macro-action-websocket.cpp
           macro-action-websocket.hpp
+          macro-action-window.cpp
+          macro-action-window.hpp
           macro-condition-audio.cpp
           macro-condition-audio.hpp
           macro-condition-cursor.cpp
diff --git a/plugins/base/macro-action-window.cpp b/plugins/base/macro-action-window.cpp
new file mode 100644
index 000000000..5791ef699
--- /dev/null
+++ b/plugins/base/macro-action-window.cpp
@@ -0,0 +1,218 @@
+#include "macro-action-window.hpp"
+#include "layout-helpers.hpp"
+#include "platform-funcs.hpp"
+#include "selection-helpers.hpp"
+
+namespace advss {
+
+const std::string MacroActionWindow::id = "window";
+
+#ifdef _WIN32
+bool MacroActionWindow::_registered = MacroActionFactory::Register(
+	MacroActionWindow::id,
+	{MacroActionWindow::Create, MacroActionWindowEdit::Create,
+	 "AdvSceneSwitcher.action.window"});
+#else
+bool MacroActionWindow::_registered = false;
+#endif // _WIN32
+
+const static std::map<MacroActionWindow::Action, std::string> actionTypes = {
+	{MacroActionWindow::Action::SET_FOCUS_WINDOW,
+	 "AdvSceneSwitcher.action.window.type.setFocusWindow"},
+	{MacroActionWindow::Action::MAXIMIZE_WINDOW,
+	 "AdvSceneSwitcher.action.window.type.maximizeWindow"},
+	{MacroActionWindow::Action::MINIMIZE_WINDOW,
+	 "AdvSceneSwitcher.action.window.type.minimizeWindow"},
+	{MacroActionWindow::Action::CLOSE_WINDOW,
+	 "AdvSceneSwitcher.action.window.type.closeWindow"},
+};
+
+#ifdef _WIN32
+void SetFocusWindow(const std::string &);
+void MaximizeWindow(const std::string &);
+void MinimizeWindow(const std::string &);
+void CloseWindow(const std::string &);
+#else
+void SetFocusWindow(const std::string &) {}
+void MaximizeWindow(const std::string &) {}
+void MinimizeWindow(const std::string &) {}
+void CloseWindow(const std::string &) {}
+#endif // _WIN32
+
+std::optional<std::string> MacroActionWindow::GetMatchingWindow() const
+{
+	std::vector<std::string> windowList;
+	GetWindowList(windowList);
+
+	if (!_regex.Enabled()) {
+		if (std::find(windowList.begin(), windowList.end(),
+			      std::string(_window)) == windowList.end()) {
+			return {};
+		}
+		return _window;
+	}
+
+	for (const auto &window : windowList) {
+		if (_regex.Matches(window, _window)) {
+			return window;
+		}
+	}
+
+	return {};
+}
+
+bool MacroActionWindow::PerformAction()
+{
+	auto window = GetMatchingWindow();
+	if (!window) {
+		return true;
+	}
+
+	switch (_action) {
+	case Action::SET_FOCUS_WINDOW:
+		SetFocusWindow(*window);
+		break;
+	case Action::MAXIMIZE_WINDOW:
+		MaximizeWindow(*window);
+		break;
+	case Action::MINIMIZE_WINDOW:;
+		MinimizeWindow(*window);
+		break;
+	case Action::CLOSE_WINDOW:
+		CloseWindow(*window);
+		break;
+	default:
+		break;
+	}
+
+	return true;
+}
+
+void MacroActionWindow::LogAction() const
+{
+	auto window = GetMatchingWindow();
+	switch (_action) {
+	case Action::CLOSE_WINDOW:
+		blog(LOG_INFO, "close window \"%s\"",
+		     window ? window->c_str() : "nothing matched");
+		break;
+	case Action::SET_FOCUS_WINDOW:
+		blog(LOG_INFO, "set focus window \"%s\"",
+		     window ? window->c_str() : "nothing matched");
+		break;
+	default:
+		break;
+	}
+}
+
+bool MacroActionWindow::Save(obs_data_t *obj) const
+{
+	MacroAction::Save(obj);
+	obs_data_set_int(obj, "action", static_cast<int>(_action));
+	_window.Save(obj, "window");
+	_regex.Save(obj, "regex");
+	return true;
+}
+
+bool MacroActionWindow::Load(obs_data_t *obj)
+{
+	MacroAction::Load(obj);
+	_action = static_cast<Action>(obs_data_get_int(obj, "action"));
+	_window.Load(obj, "window");
+	_regex.Load(obj, "regex");
+	return true;
+}
+
+std::string MacroActionWindow::GetShortDesc() const
+{
+	return _window;
+}
+
+static inline void populateActionSelection(QComboBox *list)
+{
+	for (const auto &[_, name] : actionTypes) {
+		list->addItem(obs_module_text(name.c_str()));
+	}
+}
+
+MacroActionWindowEdit::MacroActionWindowEdit(
+	QWidget *parent, std::shared_ptr<MacroActionWindow> entryData)
+	: QWidget(parent),
+	  _actions(new QComboBox()),
+	  _windows(new QComboBox()),
+	  _regex(new RegexConfigWidget(this))
+{
+	populateActionSelection(_actions);
+
+	_windows->setEditable(true);
+	_windows->setMaxVisibleItems(20);
+	PopulateWindowSelection(_windows);
+
+	QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
+			 SLOT(ActionChanged(int)));
+	QWidget::connect(_windows, SIGNAL(currentTextChanged(const QString &)),
+			 this, SLOT(WindowChanged(const QString &)));
+	QWidget::connect(_regex,
+			 SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
+			 SLOT(RegexChanged(const RegexConfig &)));
+
+	auto layout = new QHBoxLayout;
+	PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.window.entry"),
+		     layout,
+		     {{"{{actions}}", _actions},
+		      {"{{windows}}", _windows},
+		      {"{{regex}}", _regex}});
+	setLayout(layout);
+
+	_entryData = entryData;
+	UpdateEntryData();
+	_loading = false;
+}
+
+void MacroActionWindowEdit::UpdateEntryData()
+{
+	if (!_entryData) {
+		return;
+	}
+	_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
+	_windows->setCurrentText(QString::fromStdString(_entryData->_window));
+	_regex->SetRegexConfig(_entryData->_regex);
+	adjustSize();
+	updateGeometry();
+}
+
+void MacroActionWindowEdit::WindowChanged(const QString &text)
+{
+	if (_loading || !_entryData) {
+		return;
+	}
+
+	auto lock = LockContext();
+	_entryData->_window = text.toStdString();
+	emit HeaderInfoChanged(
+		QString::fromStdString(_entryData->GetShortDesc()));
+}
+
+void MacroActionWindowEdit::RegexChanged(const RegexConfig &regex)
+{
+	if (_loading || !_entryData) {
+		return;
+	}
+
+	auto lock = LockContext();
+	_entryData->_regex = regex;
+	adjustSize();
+	updateGeometry();
+}
+
+void MacroActionWindowEdit::ActionChanged(int value)
+{
+	if (_loading || !_entryData) {
+		return;
+	}
+
+	auto lock = LockContext();
+	_entryData->_action = static_cast<MacroActionWindow::Action>(value);
+}
+
+} // namespace advss
diff --git a/plugins/base/macro-action-window.hpp b/plugins/base/macro-action-window.hpp
new file mode 100644
index 000000000..ee8633b7b
--- /dev/null
+++ b/plugins/base/macro-action-window.hpp
@@ -0,0 +1,71 @@
+#pragma once
+#include "macro-action-edit.hpp"
+#include "regex-config.hpp"
+#include "variable-string.hpp"
+
+namespace advss {
+
+class MacroActionWindow : public MacroAction {
+public:
+	MacroActionWindow(Macro *m) : MacroAction(m) {}
+	bool PerformAction();
+	void LogAction() const;
+	bool Save(obs_data_t *obj) const;
+	bool Load(obs_data_t *obj);
+	std::string GetShortDesc() const;
+	std::string GetId() const { return id; };
+	static std::shared_ptr<MacroAction> Create(Macro *m)
+	{
+		return std::make_shared<MacroActionWindow>(m);
+	}
+
+	enum class Action {
+		SET_FOCUS_WINDOW,
+		MAXIMIZE_WINDOW,
+		MINIMIZE_WINDOW,
+		CLOSE_WINDOW,
+	};
+	Action _action = Action::SET_FOCUS_WINDOW;
+	StringVariable _window;
+	RegexConfig _regex;
+
+private:
+	std::optional<std::string> GetMatchingWindow() const;
+
+	static bool _registered;
+	static const std::string id;
+};
+
+class MacroActionWindowEdit : public QWidget {
+	Q_OBJECT
+
+public:
+	MacroActionWindowEdit(
+		QWidget *parent,
+		std::shared_ptr<MacroActionWindow> entryData = nullptr);
+	void UpdateEntryData();
+	static QWidget *Create(QWidget *parent,
+			       std::shared_ptr<MacroAction> action)
+	{
+		return new MacroActionWindowEdit(
+			parent,
+			std::dynamic_pointer_cast<MacroActionWindow>(action));
+	}
+
+private slots:
+	void ActionChanged(int value);
+	void WindowChanged(const QString &text);
+	void RegexChanged(const RegexConfig &);
+signals:
+	void HeaderInfoChanged(const QString &);
+
+private:
+	QComboBox *_actions;
+	QComboBox *_windows;
+	RegexConfigWidget *_regex;
+
+	std::shared_ptr<MacroActionWindow> _entryData;
+	bool _loading = true;
+};
+
+} // namespace advss
diff --git a/plugins/base/utils/windows/windows.cpp b/plugins/base/utils/windows/windows.cpp
index e82515e38..c007b92b7 100644
--- a/plugins/base/utils/windows/windows.cpp
+++ b/plugins/base/utils/windows/windows.cpp
@@ -204,6 +204,45 @@ std::string GetWindowClassByWindowTitle(const std::string &window)
 	return className;
 }
 
+void SetFocusWindow(const std::string &title)
+{
+	auto handle = getHWNDfromTitle(title);
+	if (!handle) {
+		return;
+	}
+	ShowWindow(handle, SW_RESTORE);
+	SetForegroundWindow(handle);
+	SetFocus(handle);
+	SetActiveWindow(handle);
+}
+
+void CloseWindow(const std::string &title)
+{
+	auto handle = getHWNDfromTitle(title);
+	if (!handle) {
+		return;
+	}
+	SendMessage(handle, WM_CLOSE, 0, 0);
+}
+
+void MaximizeWindow(const std::string &title)
+{
+	auto handle = getHWNDfromTitle(title);
+	if (!handle) {
+		return;
+	}
+	ShowWindow(handle, SW_MAXIMIZE);
+}
+
+void MinimizeWindow(const std::string &title)
+{
+	auto handle = getHWNDfromTitle(title);
+	if (!handle) {
+		return;
+	}
+	ShowWindow(handle, SW_MINIMIZE);
+}
+
 class RawMouseInputFilter;
 static RawMouseInputFilter *mouseInputFilter;
 static std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{};