diff --git a/src/index.ts b/src/index.ts
index 53d0322e5..964377668 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,7 +4,7 @@
  * Copyright (c) 2018, Microsoft Corporation (MIT License).
  */
 
-import { ITerminal, IPtyOpenOptions, IPtyForkOptions, IWindowsPtyForkOptions } from './interfaces';
+import { ITerminal, IPtyOpenOptions, IPtyForkOptions, IWindowsPtyForkOptions, IConptyHandoffHandles } from './interfaces';
 import { ArgvOrCommandLine } from './types';
 
 let terminalCtor: any;
@@ -44,6 +44,14 @@ export function open(options: IPtyOpenOptions): ITerminal {
   return terminalCtor.open(options);
 }
 
+export function handoff(handoff: IConptyHandoffHandles, opt?: IWindowsPtyForkOptions): ITerminal {
+  opt = opt || {};
+  opt.useConpty = true;
+  opt.useConptyDll = true;
+  opt.conptyHandoff = handoff;
+  return new terminalCtor(undefined, undefined, opt);
+}
+
 /**
  * Expose the native API when not Windows, note that this is not public API and
  * could be removed at any time.
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 207bf15d5..521e543b0 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -121,6 +121,7 @@ export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
   useConpty?: boolean;
   useConptyDll?: boolean;
   conptyInheritCursor?: boolean;
+  conptyHandoff?: IConptyHandoffHandles;
 }
 
 export interface IPtyOpenOptions {
@@ -128,3 +129,12 @@ export interface IPtyOpenOptions {
   rows?: number;
   encoding?: string | null;
 }
+
+export interface IConptyHandoffHandles {
+  input?: number;
+  output?: number;
+  signal?: number;
+  ref?: number;
+  server?: number; // ConPty process
+  client?: number; // Shell process
+}
diff --git a/src/native.d.ts b/src/native.d.ts
index 973aca57d..719c1d17b 100644
--- a/src/native.d.ts
+++ b/src/native.d.ts
@@ -8,6 +8,7 @@ interface IConptyNative {
   resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void;
   clear(ptyId: number, useConptyDll: boolean): void;
   kill(ptyId: number, useConptyDll: boolean): void;
+  handoff(input: number, output: number, signal: number, ref: number, server: number, client: number, onExitCallback: (exitCode: number) => void): IWinptyProcess;
 }
 
 interface IWinptyNative {
@@ -28,15 +29,15 @@ interface IUnixNative {
 interface IConptyProcess {
   pty: number;
   fd: number;
-  conin: string;
-  conout: string;
+  conin: string | number;
+  conout: string | number;
 }
 
 interface IWinptyProcess {
   pty: number;
   fd: number;
-  conin: string;
-  conout: string;
+  conin: string | number;
+  conout: string | number;
   pid: number;
   innerPid: number;
 }
diff --git a/src/shared/conout.ts b/src/shared/conout.ts
index 7a7e05f85..f63b1ba49 100644
--- a/src/shared/conout.ts
+++ b/src/shared/conout.ts
@@ -4,6 +4,7 @@
 
 export interface IWorkerData {
   conoutPipeName: string;
+  conoutFD?: number;
 }
 
 export const enum ConoutWorkerMessage {
diff --git a/src/win/conpty.cc b/src/win/conpty.cc
index b4b33d10b..ac0cb40db 100644
--- a/src/win/conpty.cc
+++ b/src/win/conpty.cc
@@ -12,6 +12,7 @@
 
 #define NODE_ADDON_API_DISABLE_DEPRECATED
 #include <node_api.h>
+#include <uv.h>
 #include <assert.h>
 #include <Shlwapi.h> // PathCombine, PathIsRelative
 #include <sstream>
@@ -19,6 +20,8 @@
 #include <string>
 #include <thread>
 #include <vector>
+#include <io.h>
+#include <fcntl.h>
 #include <Windows.h>
 #include <strsafe.h>
 #include "path_util.h"
@@ -34,6 +37,7 @@ typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE
 typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
 typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
 typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
+typedef HRESULT (__stdcall *PFNPACKPSEUDOCONSOLE)(HANDLE hProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);
 
 #endif
 
@@ -105,8 +109,10 @@ void SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {
     // Calling DisconnectNamedPipes here or in PtyKill results in a crash,
     // ref https://github.com/microsoft/node-pty/issues/512,
     // so we only call CloseHandle for now.
-    CloseHandle(baton->hIn);
-    CloseHandle(baton->hOut);
+    if (baton->hIn)
+      CloseHandle(baton->hIn);
+    if (baton->hOut)
+      CloseHandle(baton->hOut);
 
     auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
     switch (status) {
@@ -552,6 +558,65 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
   return env.Undefined();
 }
 
+static Napi::Value PtyHandoff(const Napi::CallbackInfo& info) {
+  Napi::Env env(info.Env());
+  Napi::HandleScope scope(env);
+
+  if (info.Length() != 7 ||
+      !info[0].IsNumber() ||
+      !info[1].IsNumber() ||
+      !info[2].IsNumber() ||
+      !info[3].IsNumber() ||
+      !info[4].IsNumber() ||
+      !info[5].IsNumber() ||
+      !info[6].IsFunction()) {
+    throw Napi::Error::New(env, "Usage: pty.handoff(input, output, signal, ref, server, client, exitCallback)");
+  }
+
+  HANDLE hIn = reinterpret_cast<HANDLE>(info[0].As<Napi::Number>().Int64Value());
+  HANDLE hOut = reinterpret_cast<HANDLE>(info[1].As<Napi::Number>().Int64Value());
+  HANDLE hSig = reinterpret_cast<HANDLE>(info[2].As<Napi::Number>().Int64Value());
+  HANDLE hRef = reinterpret_cast<HANDLE>(info[3].As<Napi::Number>().Int64Value());
+  HANDLE hServerProcess = reinterpret_cast<HANDLE>(info[4].As<Napi::Number>().Int64Value());
+  HANDLE hClientProcess = reinterpret_cast<HANDLE>(info[5].As<Napi::Number>().Int64Value());
+  Napi::Function exitCallback = info[6].As<Napi::Function>();
+
+  HPCON hpc = nullptr;
+
+  HANDLE hLibrary = LoadConptyDll(info, true);
+  if (hLibrary != nullptr)
+  {
+    PFNPACKPSEUDOCONSOLE const pfnPackPseudoConsole = (PFNPACKPSEUDOCONSOLE)GetProcAddress(
+      (HMODULE)hLibrary,
+      "ConptyPackPseudoConsole");
+    if (pfnPackPseudoConsole)
+    {
+      pfnPackPseudoConsole(hServerProcess, hRef, hSig, &hpc);
+    }
+  }
+
+  if (!hpc) {
+    throw Napi::Error::New(env, "Failed to handoff conpty");
+  }
+
+  const int ptyId = InterlockedIncrement(&ptyCounter);
+  pty_baton* handle = new pty_baton(ptyId, nullptr, nullptr, hpc);
+  handle->hShell = hClientProcess;
+  ptyHandles.insert(ptyHandles.end(), handle);
+
+  SetupExitCallback(env, exitCallback, handle);
+
+  Napi::Object marshal = Napi::Object::New(env);
+  marshal.Set("pty", Napi::Number::New(env, ptyId));
+  marshal.Set("fd", Napi::Number::New(env, -1));
+  marshal.Set("conin", Napi::Number::New(env, uv_open_osfhandle(hIn)));
+  marshal.Set("conout", Napi::Number::New(env, uv_open_osfhandle(hOut)));
+  marshal.Set("innerPid", Napi::Number::New(env, GetProcessId(hClientProcess)));
+  marshal.Set("pid", Napi::Number::New(env, GetProcessId(hServerProcess)));
+
+  return marshal;
+}
+
 /**
 * Init
 */
@@ -562,6 +627,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
   exports.Set("resize", Napi::Function::New(env, PtyResize));
   exports.Set("clear", Napi::Function::New(env, PtyClear));
   exports.Set("kill", Napi::Function::New(env, PtyKill));
+  exports.Set("handoff", Napi::Function::New(env, PtyHandoff));
   return exports;
 };
 
diff --git a/src/windowsConoutConnection.ts b/src/windowsConoutConnection.ts
index de0b6b498..7f338f5b2 100644
--- a/src/windowsConoutConnection.ts
+++ b/src/windowsConoutConnection.ts
@@ -37,9 +37,10 @@ export class ConoutConnection implements IDisposable {
   public get onReady(): IEvent<void> { return this._onReady.event; }
 
   constructor(
-    private _conoutPipeName: string
+    private _conoutPipeName: string,
+    conoutFD: number | undefined,
   ) {
-    const workerData: IWorkerData = { conoutPipeName: _conoutPipeName };
+    const workerData: IWorkerData = { conoutPipeName: _conoutPipeName, conoutFD };
     const scriptPath = __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked');
     this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.js'), { workerData });
     this._worker.on('message', (message: ConoutWorkerMessage) => {
diff --git a/src/windowsPtyAgent.ts b/src/windowsPtyAgent.ts
index 5e8c062db..37d8e96a0 100644
--- a/src/windowsPtyAgent.ts
+++ b/src/windowsPtyAgent.ts
@@ -11,10 +11,22 @@ import { Socket } from 'net';
 import { ArgvOrCommandLine } from './types';
 import { fork } from 'child_process';
 import { ConoutConnection } from './windowsConoutConnection';
+import { IConptyHandoffHandles } from './interfaces';
 
 let conptyNative: IConptyNative;
 let winptyNative: IWinptyNative;
 
+export interface IWindowsPtyAgentOptions {
+  file?: string;
+  args?: ArgvOrCommandLine;
+  env?: string[];
+  cwd?: string;
+  cols?: number;
+  rows?: number;
+
+  handoff?: IConptyHandoffHandles;
+}
+
 /**
  * The amount of time to wait for additional data after the conpty shell process has exited before
  * shutting down the socket. The timer will be reset if a new data event comes in after the timer
@@ -46,12 +58,7 @@ export class WindowsPtyAgent {
   public get pty(): number { return this._pty; }
 
   constructor(
-    file: string,
-    args: ArgvOrCommandLine,
-    env: string[],
-    cwd: string,
-    cols: number,
-    rows: number,
+    opts: IWindowsPtyAgentOptions,
     debug: boolean,
     private _useConpty: boolean | undefined,
     private _useConptyDll: boolean = false,
@@ -91,20 +98,39 @@ export class WindowsPtyAgent {
     }
     this._ptyNative = this._useConpty ? conptyNative : winptyNative;
 
-    // Sanitize input variable.
-    cwd = path.resolve(cwd);
+    const handoff = !!opts.handoff;
+    let commandLine: string, cwd: string, env: string[];
+    let term: IConptyProcess | IWinptyProcess;
 
-    // Compose command line
-    const commandLine = argsToCommandLine(file, args);
+    if (handoff) {
+      if (!this._useConpty || !this._useConptyDll) {
+        throw new Error('Terminal handoff requires conpty and conpty dll');
+      }
+
+      const { input, output, signal, ref, server, client } = opts.handoff!;
+
+      term = (this._ptyNative as IConptyNative).handoff(input!, output!, signal!, ref!, server!, client!, c => this._$onProcessExit(c)); // borrow IWinptyProcess
 
-    // Open pty session.
-    let term: IConptyProcess | IWinptyProcess;
-    if (this._useConpty) {
-      term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
-    } else {
-      term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
       this._pid = (term as IWinptyProcess).pid;
       this._innerPid = (term as IWinptyProcess).innerPid;
+    } else {
+      const { file, args, cols, rows } = opts;
+      env = opts.env!;
+
+      // Sanitize input variable.
+      cwd = path.resolve(opts.cwd!);
+
+      // Compose command line
+      commandLine = argsToCommandLine(file!, args!);
+
+      // Open pty session.
+      if (this._useConpty) {
+        term = (this._ptyNative as IConptyNative).startProcess(file!, cols!, rows!, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
+      } else {
+        term = (this._ptyNative as IWinptyNative).startProcess(file!, commandLine, env!, cwd, cols!, rows!, debug);
+        this._pid = (term as IWinptyProcess).pid;
+        this._innerPid = (term as IWinptyProcess).innerPid;
+      }
     }
 
     // Not available on windows.
@@ -118,7 +144,13 @@ export class WindowsPtyAgent {
     this._outSocket = new Socket();
     this._outSocket.setEncoding('utf8');
     // The conout socket must be ready out on another thread to avoid deadlocks
-    this._conoutSocketWorker = new ConoutConnection(term.conout);
+    let { conout } = term;
+    let conoutFD: number | undefined;
+    if (typeof conout === 'number') {
+      conoutFD = conout;
+      conout = "\\\\.\\pipe\\" + this._generatePipeName() + "-out";
+    }
+    this._conoutSocketWorker = new ConoutConnection(conout, conoutFD);
     this._conoutSocketWorker.onReady(() => {
       this._conoutSocketWorker.connectSocket(this._outSocket);
     });
@@ -126,7 +158,7 @@ export class WindowsPtyAgent {
       this._outSocket.emit('ready_datapipe');
     });
 
-    const inSocketFD = fs.openSync(term.conin, 'w');
+    const inSocketFD = typeof term.conin === 'number' ? term.conin : fs.openSync(term.conin, 'w');
     this._inSocket = new Socket({
       fd: inSocketFD,
       readable: false,
@@ -134,8 +166,8 @@ export class WindowsPtyAgent {
     });
     this._inSocket.setEncoding('utf8');
 
-    if (this._useConpty) {
-      const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, c => this._$onProcessExit(c));
+    if (this._useConpty && !handoff) {
+      const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine!, cwd!, env!, c => this._$onProcessExit(c));
       this._innerPid = connect.pid;
     }
   }
diff --git a/src/windowsTerminal.ts b/src/windowsTerminal.ts
index 2ef9feddb..8ac762f24 100644
--- a/src/windowsTerminal.ts
+++ b/src/windowsTerminal.ts
@@ -6,7 +6,7 @@
 
 import { Socket } from 'net';
 import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
-import { WindowsPtyAgent } from './windowsPtyAgent';
+import { WindowsPtyAgent, IWindowsPtyAgentOptions } from './windowsPtyAgent';
 import { IPtyOpenOptions, IWindowsPtyForkOptions } from './interfaces';
 import { ArgvOrCommandLine } from './types';
 import { assign } from './utils';
@@ -25,21 +25,27 @@ export class WindowsTerminal extends Terminal {
     this._checkType('args', args, 'string', true);
 
     // Initialize arguments
-    args = args || [];
-    file = file || DEFAULT_FILE;
     opt = opt || {};
-    opt.env = opt.env || process.env;
+
+    const agentOpt: IWindowsPtyAgentOptions = {};
+    if (opt.conptyHandoff) {
+      agentOpt.handoff = opt.conptyHandoff;
+      this._name = opt.name || DEFAULT_NAME;
+    } else {
+      agentOpt.args = args || [];
+      this._file = agentOpt.file = file || DEFAULT_FILE;
+      const env = assign({}, opt.env || process.env);
+      this._name = opt.name || env.TERM || DEFAULT_NAME;
+      agentOpt.env = this._parseEnv(env);
+      agentOpt.cwd = opt.cwd || process.cwd();
+    }
 
     if (opt.encoding) {
       console.warn('Setting encoding on Windows is not supported');
     }
 
-    const env = assign({}, opt.env);
-    this._cols = opt.cols || DEFAULT_COLS;
-    this._rows = opt.rows || DEFAULT_ROWS;
-    const cwd = opt.cwd || process.cwd();
-    const name = opt.name || env.TERM || DEFAULT_NAME;
-    const parsedEnv = this._parseEnv(env);
+    this._cols = agentOpt.cols = opt.cols || DEFAULT_COLS;
+    this._rows = agentOpt.rows = opt.rows || DEFAULT_ROWS;
 
     // If the terminal is ready
     this._isReady = false;
@@ -48,7 +54,7 @@ export class WindowsTerminal extends Terminal {
     this._deferreds = [];
 
     // Create new termal.
-    this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
+    this._agent = new WindowsPtyAgent(agentOpt, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
     this._socket = this._agent.outSocket;
 
     // Not available until `ready` event emitted.
@@ -114,9 +120,6 @@ export class WindowsTerminal extends Terminal {
 
     });
 
-    this._file = file;
-    this._name = name;
-
     this._readable = true;
     this._writable = true;
 
diff --git a/src/worker/conoutSocketWorker.ts b/src/worker/conoutSocketWorker.ts
index cedd86fe2..cf7639a7f 100644
--- a/src/worker/conoutSocketWorker.ts
+++ b/src/worker/conoutSocketWorker.ts
@@ -6,10 +6,14 @@ import { parentPort, workerData } from 'worker_threads';
 import { Socket, createServer } from 'net';
 import { ConoutWorkerMessage, IWorkerData, getWorkerPipeName } from '../shared/conout';
 
-const conoutPipeName = (workerData as IWorkerData).conoutPipeName;
-const conoutSocket = new Socket();
+const { conoutPipeName, conoutFD } = workerData as IWorkerData;
+const conoutSocket = new Socket(conoutFD ? {
+  fd: conoutFD,
+  readable: true,
+  writable: false
+} : undefined);
 conoutSocket.setEncoding('utf8');
-conoutSocket.connect(conoutPipeName, () => {
+const onConnect = () => {
   const server = createServer(workerSocket => {
     conoutSocket.pipe(workerSocket);
   });
@@ -19,4 +23,9 @@ conoutSocket.connect(conoutPipeName, () => {
     throw new Error('worker_threads parentPort is null');
   }
   parentPort.postMessage(ConoutWorkerMessage.READY);
-});
+};
+if (conoutFD) {
+  onConnect();
+} else {
+  conoutSocket.connect(conoutPipeName, onConnect);
+}
diff --git a/typings/node-pty.d.ts b/typings/node-pty.d.ts
index 7a14ec647..fed34b4f0 100644
--- a/typings/node-pty.d.ts
+++ b/typings/node-pty.d.ts
@@ -17,6 +17,8 @@ declare module 'node-pty' {
    */
   export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
 
+  export function handoff(handoff: IConptyHandoffHandles, opt?: IWindowsPtyForkOptions): IPty;
+
   export interface IBasePtyForkOptions {
 
     /**
@@ -110,6 +112,15 @@ declare module 'node-pty' {
     conptyInheritCursor?: boolean;
   }
 
+  export interface IConptyHandoffHandles {
+    input?: number;
+    output?: number;
+    signal?: number;
+    ref?: number;
+    server?: number; // ConPty process
+    client?: number; // Shell process
+  }
+
   /**
    * An interface representing a pseudoterminal, on Windows this is emulated via the winpty library.
    */