2Copyright 2019 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
17var PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
18var EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
19var HEAP_ZERO_MEMORY = 0x00000008;
21var duplex = require('stream').Duplex;
25 this._ObjectID = 'win-virtual-terminal';
26 Object.defineProperty(this, 'supported', {
29 var gm = require('_GenericMarshal');
30 var k32 = gm.CreateNativeProxy('kernel32.dll');
33 k32.CreateMethod('CreatePseudoConsole');
42 this.Create = function Create(path, width, height)
44 if (!this.supported) { throw ('This build of Windows does not have support for PseudoConsoles'); }
45 if (!width) { width = 80; }
46 if (!height) { height = 25; }
48 var GM = require('_GenericMarshal');
49 var k32 = GM.CreateNativeProxy('kernel32.dll');
50 k32.CreateMethod('CancelIoEx'); // https://learn.microsoft.com/en-us/windows/win32/fileio/cancelioex-func
51 k32.CreateMethod('CreatePipe'); // https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
52 k32.CreateMethod('CreateProcessW'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
53 k32.CreateMethod('CreatePseudoConsole'); // https://learn.microsoft.com/en-us/windows/console/createpseudoconsole
54 k32.CreateMethod('CloseHandle'); // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
55 k32.CreateMethod('ClosePseudoConsole'); // https://learn.microsoft.com/en-us/windows/console/closepseudoconsole
56 k32.CreateMethod('GetProcessHeap'); // https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-getprocessheap
57 k32.CreateMethod('HeapAlloc'); // https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc
58 k32.CreateMethod('InitializeProcThreadAttributeList'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist
59 k32.CreateMethod('ResizePseudoConsole'); // https://learn.microsoft.com/en-us/windows/console/resizepseudoconsole
60 k32.CreateMethod('UpdateProcThreadAttribute'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute
61 k32.CreateMethod('WriteFile'); // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
62 k32.CreateMethod('ReadFile'); // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
63 k32.CreateMethod('TerminateProcess'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess
65 var ret = { _h: GM.CreatePointer(), _consoleInput: GM.CreatePointer(), _consoleOutput: GM.CreatePointer(), _input: GM.CreatePointer(), _output: GM.CreatePointer(), k32: k32 };
66 var attrSize = GM.CreateVariable(8);
68 var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24);
70 // Create the necessary pipes
71 if (k32.CreatePipe(ret._consoleInput, ret._input, 0, 0).Val == 0) { console.log('PIPE/FAIL'); }
72 if (k32.CreatePipe(ret._output, ret._consoleOutput, 0, 0).Val == 0) { console.log('PIPE/FAIL'); }
75 if (k32.CreatePseudoConsole((height << 16) | width, ret._consoleInput.Deref(), ret._consoleOutput.Deref(), 0, ret._h).Val != 0)
77 throw ('Error calling CreatePseudoConsole()');
81 // Reference for STARTUPINFOEXW
82 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-startupinfoexw
86 // Reference for STARTUPINFOW
87 // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
90 k32.InitializeProcThreadAttributeList(0, 1, 0, attrSize);
91 attrList = GM.CreateVariable(attrSize.toBuffer().readUInt32LE());
92 var startupinfoex = GM.CreateVariable(GM.PointerSize == 8 ? 112 : 72); // Create Structure, 64 bits is 112 bytes, 32 bits is 72 bytes
93 startupinfoex.toBuffer().writeUInt32LE(GM.PointerSize == 8 ? 112 : 72, 0); // Write buffer size
94 attrList.pointerBuffer().copy(startupinfoex.Deref(GM.PointerSize == 8 ? 104 : 68, GM.PointerSize).toBuffer()); // Write the reference to STARTUPINFOEX
96 if (k32.InitializeProcThreadAttributeList(attrList, 1, 0, attrSize).Val != 0)
98 if (k32.UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, ret._h.Deref(), GM.PointerSize, 0, 0).Val != 0)
100 if (k32.CreateProcessW(0, GM.CreateVariable(path, { wide: true }), 0, 0, 1, EXTENDED_STARTUPINFO_PRESENT, 0, 0, startupinfoex, pi).Val != 0) // Create the process to run in the pseudoconsole
103 // Create a Stream Object, to be able to read/write data to the pseudoconsole
105 ret._startupinfoex = startupinfoex;
106 ret._process = pi.Deref(0);
107 ret._pid = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE();
110 'write': function (chunk, flush)
112 var written = require('_GenericMarshal').CreateVariable(4);
113 this.terminal.k32.WriteFile(this.terminal._input.Deref(), require('_GenericMarshal').CreateVariable(chunk), chunk.length, written, 0);
117 'final': function (flush)
119 if (this.terminal._process)
121 this.terminal._process = null;
122 k32.ClosePseudoConsole(this._obj._h.Deref());
129 // The ProcessInfo object is signaled when the process exits
132 ret._waiter = require('DescriptorEvents').addDescriptor(pi.Deref(0));
134 ret._waiter._obj = ret;
135 ret._waiter.on('signaled', function ()
137 k32.CancelIoEx(this._obj._output.Deref(), 0);
139 // Child process has exited
142 if (this._obj._process)
144 this._obj._process = null;
145 k32.ClosePseudoConsole(this._obj._h.Deref());
147 k32.CloseHandle(this._obj._input.Deref());
148 k32.CloseHandle(this._obj._output.Deref());
150 k32.CloseHandle(this._obj._consoleInput.Deref());
151 k32.CloseHandle(this._obj._consoleOutput.Deref());
153 ds.resizeTerminal = function (w, h)
155 console.setDestination(console.Destinations.LOGFILE);
156 console.log('resizeTerminal(' + w + ', ' + h + ')');
158 if((hr=k32.ResizePseudoConsole(this._obj._h.Deref(), (h << 16) | w).Val) != 0)
160 console.log('HResult=' + hr);
161 throw ('Resize returned HRESULT: ' + hr);
163 console.log('SUCCESS');
167 ds._rpbuf = GM.CreateVariable(4096);
168 ds._rpbufRead = GM.CreateVariable(4);
169 ds.__read = function __read()
171 // Asyncronously read data from the pseudoconsole
172 this._rp = this.terminal.k32.ReadFile.async(this.terminal._output.Deref(), this._rpbuf, this._rpbuf._size, this._rpbufRead, 0);
173 this._rp.then(function ()
175 var len = this.parent._rpbufRead.toBuffer().readUInt32LE();
176 if (len <= 0) { return; }
178 this.parent.push(this.parent._rpbuf.toBuffer().slice(0, len));
179 this.parent.__read();
181 this._rp.parent = this;
192 throw ('Internal Error');
195 // This evaluates whether or not the powershell binary exists
196 this.PowerShellCapable = function ()
198 if (require('os').arch() == 'x64')
200 return (require('fs').existsSync(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe'));
204 return (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'));
208 // Start the PseudoConsole with the Command Prompt
209 this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
211 return (this.Create(process.env['windir'] + '\\System32\\cmd.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
214 // Start the PseduoConsole with PowerShell
215 this.StartPowerShell = function StartPowerShell(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
217 if (require('os').arch() == 'x64')
219 if (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'))
221 return (this.Create(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
225 return (this.Create(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
230 return (this.Create(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
235if (process.platform == 'win32')
237 module.exports = new vt();