diff --git a/src/build/Common.gmake b/src/build/Common.gmake
index 4ecb6b60ff1258eb2fe08f92089b03cf7ace8871..081cf8867432c0080f6aecf8872dbace2c51461b 100644
--- a/src/build/Common.gmake
+++ b/src/build/Common.gmake
@@ -246,7 +246,12 @@ ifndef os
 endif
 os      :=	$(shell echo $(os) | tr '[A-Z]' '[a-z]' | tr ' ' '_')
 
-ifndef win
+ifdef win
+ WINNT ?= 0x0501
+ WINVER ?= 0x0501
+ WIN32_IE ?= 0x0500
+ NTDDI ?= 0x0500
+else
  CFLAGS +=	-DPREFER_POLL
  machine_uname	:=	$(shell if uname -m | egrep -v "(i[3456789]*|x)86" > /dev/null; then uname -m | tr "[A-Z]" "[a-z]" | tr " " "_" ; fi)
  machine_uname	:=	$(shell if uname -m | egrep "64" > /dev/null; then uname -m | tr "[A-Z]" "[a-z]" | tr " " "_" ; else echo $(machine_uname) ; fi)
@@ -445,7 +450,7 @@ else
       #CFLAGS	   += -D_POSIX_PTHREAD_SEMANTICS
      else
       ifdef win # Windows
-       CFLAGS		+= -U__STRICT_ANSI__ -D_WIN32 -D_WIN32_WINNT=0x0501 -DWINVER=0x0501 -D_WIN32_IE=0x0500
+       CFLAGS		+= -U__STRICT_ANSI__ -D_WIN32 -D_WIN32_WINNT=${WINNT} -DWINVER=${WINVER} -D_WIN32_IE=${WIN32_IE} -DNTDDI_VERSION=${NTDDI}
        MT_CFLAGS	+= -D_WIN32
       else
        ifeq ($(os),emscripten)
diff --git a/src/syncterm/GNUmakefile b/src/syncterm/GNUmakefile
index 6ee7de255001da404848a718e77e1c2264410fdc..8dba0988ad8fb2cb2db75ba53a36bfd945d869b2 100644
--- a/src/syncterm/GNUmakefile
+++ b/src/syncterm/GNUmakefile
@@ -1,4 +1,8 @@
 SRC_ROOT	:=	..
+WINVER ?= 0xA00
+WINNT ?= 0xA00
+WIN32_IE ?= 0xA00
+NTDDI ?= 0x0A000006
 include ${SRC_ROOT}/build/Common.gmake
 
 ifdef NEED_BITMAP
@@ -46,6 +50,7 @@ ifdef win
  CFLAGS += -DWITH_JPEG_XL -DWITH_STATIC_JXL
  CFLAGS += -DWITH_JPEG_XL_THREADS
  OBJS += $(MTOBJODIR)$(DIRSEP)libjxl$(OFILE)
+ OBJS += $(MTOBJODIR)$(DIRSEP)conn_conpty$(OFILE)
 else
  ifeq ($(shell pkg-config libjxl --exists && echo YES), YES)
   CFLAGS += $(shell pkg-config libjxl --cflags)
diff --git a/src/syncterm/conn.c b/src/syncterm/conn.c
index afac27f6344707fd81bf1bd57479b37cf2b600c8..67eab3f01c42b843c304c765bcc3e4a2830d6522 100644
--- a/src/syncterm/conn.c
+++ b/src/syncterm/conn.c
@@ -44,6 +44,9 @@
 #ifdef __unix__
  #include "conn_pty.h"
 #endif
+#ifdef _WIN32
+ #include "conn_conpty.h"
+#endif
 #include "conn_telnet.h"
 
 #ifdef _MSC_VER
@@ -417,6 +420,12 @@ conn_connect(struct bbslist *bbs)
 			conn_api.connect = pty_connect;
 			conn_api.close = pty_close;
 			break;
+#endif
+#ifdef HAS_CONPTY
+		case CONN_TYPE_SHELL:
+			conn_api.connect = conpty_connect;
+			conn_api.close = conpty_close;
+			break;
 #endif
 		default:
 			sprintf(str, "%s connections not supported.", conn_types[bbs->conn_type]);
diff --git a/src/syncterm/conn_conpty.c b/src/syncterm/conn_conpty.c
new file mode 100644
index 0000000000000000000000000000000000000000..50d8fba04392e9682c9fe2cf4a23dde2bf7b0af5
--- /dev/null
+++ b/src/syncterm/conn_conpty.c
@@ -0,0 +1,251 @@
+#if NTDDI_VERSION >= 0x0A000006
+
+#define WIN32_LEAN_AND_MEAN
+#include <stdatomic.h>
+#include <windows.h>
+#include <wincon.h>
+
+#include "bbslist.h"
+#include "conn.h"
+#include "uifcinit.h"
+#include "window.h"
+
+HANDLE inputRead, inputWrite, outputRead, outputWrite;
+PROCESS_INFORMATION pi;
+HPCON cpty;
+
+static atomic_bool terminate;
+
+static void
+conpty_input_thread(void *args)
+{
+	DWORD  rd;
+	int    buffered;
+	size_t buffer;
+	int    i;
+	DWORD  ec;
+
+	SetThreadName("PTY Input");
+	conn_api.input_thread_running = 1;
+	while (!terminate && !conn_api.terminate) {
+		if (GetExitCodeProcess(pi.hProcess, &ec)) {
+			if (ec != STILL_ACTIVE)
+				break;
+		}
+		else {
+			break;
+		}
+		if (!ReadFile(outputRead, conn_api.rd_buf, conn_api.rd_buf_size, &rd, NULL)) {
+			break;
+		}
+		buffered = 0;
+		while (!terminate && !conn_api.terminate && buffered < rd) {
+			pthread_mutex_lock(&(conn_inbuf.mutex));
+			buffer = conn_buf_wait_free(&conn_inbuf, rd - buffered, 100);
+			buffered += conn_buf_put(&conn_inbuf, conn_api.rd_buf + buffered, buffer);
+			pthread_mutex_unlock(&(conn_inbuf.mutex));
+		}
+	}
+	terminate = true;
+	conn_api.input_thread_running = 2;
+}
+
+static void
+conpty_output_thread(void *args)
+{
+	int   wr;
+	DWORD ret;
+	int   sent;
+	DWORD ec;
+
+	SetThreadName("PTY Output");
+	conn_api.output_thread_running = 1;
+	while (!terminate && !conn_api.terminate) {
+		if (GetExitCodeProcess(pi.hProcess, &ec)) {
+			if (ec != STILL_ACTIVE)
+				break;
+		}
+		else {
+			break;
+		}
+		pthread_mutex_lock(&(conn_outbuf.mutex));
+		ret = 0;
+		wr = conn_buf_wait_bytes(&conn_outbuf, 1, 100);
+		if (wr) {
+			wr = conn_buf_get(&conn_outbuf, conn_api.wr_buf, conn_api.wr_buf_size);
+			pthread_mutex_unlock(&(conn_outbuf.mutex));
+			sent = 0;
+			while (!terminate && !conn_api.terminate && sent < wr) {
+				if (!WriteFile(inputWrite, conn_api.wr_buf + sent, wr - sent, &ret, NULL)) {
+					terminate = true;
+					break;
+				}
+				sent += ret;
+			}
+		}
+		else {
+			pthread_mutex_unlock(&(conn_outbuf.mutex));
+		}
+	}
+	conn_api.output_thread_running = 2;
+}
+
+int conpty_connect(struct bbslist *bbs)
+{
+	HANDLE heap = GetProcessHeap();
+
+	int w, h;
+	get_term_win_size(&w, &h, NULL, NULL, &bbs->nostatus);
+
+	COORD size = {
+		.X = w,
+		.Y = h
+	};
+	STARTUPINFOEXA si = {
+		.StartupInfo = {
+			.cb = sizeof(STARTUPINFOEXA)
+		}
+	};
+	size_t sz;
+	// "Note  This initial call will return an error by design. This is expected behavior."
+	!InitializeProcThreadAttributeList(NULL, 1, 0, &sz);
+	si.lpAttributeList = HeapAlloc(heap, 0, sz);
+	if (si.lpAttributeList == NULL) {
+		uifcmsg("TODO", "HeapAlloc Failed");
+		return -1;
+	}
+
+	char *cmd = bbs->addr;
+	if (cmd[0] == 0)
+		cmd = getenv("ComSpec");
+	if (cmd == NULL)  {
+		uifcmsg("TODO", "cmd Failed");
+		return -1;
+	}
+	if (!CreatePipe(&inputRead, &inputWrite, NULL, 0)) {
+		uifcmsg("TODO", "CreatePipe (input) Failed");
+		return -1;
+	}
+	if (!CreatePipe(&outputRead, &outputWrite, NULL, 0)) {
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		HeapFree(heap, 0, si.lpAttributeList);
+		uifcmsg("TODO", "CreatePipe (output) Failed");
+		return -1;
+	}
+	if (FAILED(CreatePseudoConsole(size, inputRead, outputWrite, 0, &cpty))) {
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		HeapFree(heap, 0, si.lpAttributeList);
+		uifcmsg("TODO", "CreatePseudoConsole Failed");
+		return -1;
+	}
+	if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &sz)) {
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		HeapFree(heap, 0, si.lpAttributeList);
+		uifcmsg("TODO", "InitializeProcThreadAttributeList2 Failed");
+		return -1;
+	}
+
+	if (!UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, cpty, sizeof(cpty), NULL, NULL)) {
+		DeleteProcThreadAttributeList(si.lpAttributeList);
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		HeapFree(heap, 0, si.lpAttributeList);
+		uifcmsg("TODO", "UpdateProcThreadAttribute Failed");
+		return -1;
+	}
+
+	if (!CreateProcessA(NULL, cmd, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi)) {
+		DeleteProcThreadAttributeList(si.lpAttributeList);
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		HeapFree(heap, 0, si.lpAttributeList);
+		uifcmsg("TODO", "CreateProcessA Failed");
+		return -1;
+	}
+	DeleteProcThreadAttributeList(si.lpAttributeList);
+	HeapFree(heap, 0, si.lpAttributeList);
+	if (!create_conn_buf(&conn_inbuf, BUFFER_SIZE)) {
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		uifcmsg("TODO", "create_conn_buf (input) Failed");
+		return -1;
+	}
+	if (!create_conn_buf(&conn_outbuf, BUFFER_SIZE)) {
+		destroy_conn_buf(&conn_inbuf);
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		uifcmsg("TODO", "create_conn_buf (output) Failed");
+		return -1;
+	}
+	if (!(conn_api.rd_buf = (unsigned char *)malloc(BUFFER_SIZE))) {
+		destroy_conn_buf(&conn_inbuf);
+		destroy_conn_buf(&conn_outbuf);
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		uifcmsg("TODO", "malloc (input) Failed");
+		return -1;
+	}
+	conn_api.rd_buf_size = BUFFER_SIZE;
+	if (!(conn_api.wr_buf = (unsigned char *)malloc(BUFFER_SIZE))) {
+		free(conn_api.rd_buf);
+		destroy_conn_buf(&conn_inbuf);
+		destroy_conn_buf(&conn_outbuf);
+		CloseHandle(inputRead);
+		CloseHandle(inputWrite);
+		CloseHandle(outputRead);
+		CloseHandle(outputWrite);
+		uifcmsg("TODO", "malloc (output) Failed");
+		return -1;
+	}
+	conn_api.wr_buf_size = BUFFER_SIZE;
+
+	_beginthread(conpty_output_thread, 0, NULL);
+	_beginthread(conpty_input_thread, 0, NULL);
+
+	return 0;
+}
+
+int
+conpty_close(void)
+{
+	char garbage[1024];
+	DWORD ret;
+
+	conn_api.terminate = 1;
+	terminate = true;
+	TerminateProcess(pi.hProcess, 0);
+	WaitForSingleObject(pi.hProcess, 1000);
+	ClosePseudoConsole(cpty);
+	WriteFile(outputWrite, "Die", 3, &ret, NULL);
+	while (conn_api.input_thread_running == 1 || conn_api.output_thread_running == 1) {
+		conn_recv_upto(garbage, sizeof(garbage), 0);
+		SLEEP(1);
+	}
+	destroy_conn_buf(&conn_inbuf);
+	destroy_conn_buf(&conn_outbuf);
+	FREE_AND_NULL(conn_api.rd_buf);
+	FREE_AND_NULL(conn_api.wr_buf);
+	CloseHandle(inputRead);
+	CloseHandle(inputWrite);
+	CloseHandle(outputRead);
+	CloseHandle(outputWrite);
+}
+
+#endif
diff --git a/src/syncterm/conn_conpty.h b/src/syncterm/conn_conpty.h
new file mode 100644
index 0000000000000000000000000000000000000000..25812845815f3b62e6751fca115ee15a01a22ff8
--- /dev/null
+++ b/src/syncterm/conn_conpty.h
@@ -0,0 +1,13 @@
+#ifndef CONN_CONPTY_H
+#define CONN_CONPTY_H
+#ifdef _WIN32
+#if NTDDI_VERSION >= 0x0A000006
+
+#define HAS_CONPTY
+
+int conpty_connect(struct bbslist *bbs);
+int conpty_close(void);
+
+#endif
+#endif
+#endif