EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
win-virtual-terminal.js
Go to the documentation of this file.
1/*
2Copyright 2019 Intel Corporation
3
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
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
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.
15*/
16
17var PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
18var EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
19var HEAP_ZERO_MEMORY = 0x00000008;
20
21var duplex = require('stream').Duplex;
22
23function vt()
24{
25 this._ObjectID = 'win-virtual-terminal';
26 Object.defineProperty(this, 'supported', {
27 value: (function ()
28 {
29 var gm = require('_GenericMarshal');
30 var k32 = gm.CreateNativeProxy('kernel32.dll');
31 try
32 {
33 k32.CreateMethod('CreatePseudoConsole');
34 }
35 catch(e)
36 {
37 return (false);
38 }
39 return (true);
40 })()
41 });
42 this.Create = function Create(path, width, height)
43 {
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; }
47
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
64
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);
67 var attrList;
68 var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24);
69
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'); }
73
74
75 if (k32.CreatePseudoConsole((height << 16) | width, ret._consoleInput.Deref(), ret._consoleOutput.Deref(), 0, ret._h).Val != 0)
76 {
77 throw ('Error calling CreatePseudoConsole()');
78 }
79
80 //
81 // Reference for STARTUPINFOEXW
82 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-startupinfoexw
83 //
84
85 //
86 // Reference for STARTUPINFOW
87 // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
88 //
89
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
95
96 if (k32.InitializeProcThreadAttributeList(attrList, 1, 0, attrSize).Val != 0)
97 {
98 if (k32.UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, ret._h.Deref(), GM.PointerSize, 0, 0).Val != 0)
99 {
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
101 {
102 //
103 // Create a Stream Object, to be able to read/write data to the pseudoconsole
104 //
105 ret._startupinfoex = startupinfoex;
106 ret._process = pi.Deref(0);
107 ret._pid = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE();
108 var ds = new duplex(
109 {
110 'write': function (chunk, flush)
111 {
112 var written = require('_GenericMarshal').CreateVariable(4);
113 this.terminal.k32.WriteFile(this.terminal._input.Deref(), require('_GenericMarshal').CreateVariable(chunk), chunk.length, written, 0);
114 flush();
115 return (true);
116 },
117 'final': function (flush)
118 {
119 if (this.terminal._process)
120 {
121 this.terminal._process = null;
122 k32.ClosePseudoConsole(this._obj._h.Deref());
123 }
124 flush();
125 }
126 });
127
128 //
129 // The ProcessInfo object is signaled when the process exits
130 //
131 ds._obj = ret;
132 ret._waiter = require('DescriptorEvents').addDescriptor(pi.Deref(0));
133 ret._waiter.ds = ds;
134 ret._waiter._obj = ret;
135 ret._waiter.on('signaled', function ()
136 {
137 k32.CancelIoEx(this._obj._output.Deref(), 0);
138
139 // Child process has exited
140 this.ds.push(null);
141
142 if (this._obj._process)
143 {
144 this._obj._process = null;
145 k32.ClosePseudoConsole(this._obj._h.Deref());
146 }
147 k32.CloseHandle(this._obj._input.Deref());
148 k32.CloseHandle(this._obj._output.Deref());
149
150 k32.CloseHandle(this._obj._consoleInput.Deref());
151 k32.CloseHandle(this._obj._consoleOutput.Deref());
152 });
153 ds.resizeTerminal = function (w, h)
154 {
155 console.setDestination(console.Destinations.LOGFILE);
156 console.log('resizeTerminal(' + w + ', ' + h + ')');
157 var hr;
158 if((hr=k32.ResizePseudoConsole(this._obj._h.Deref(), (h << 16) | w).Val) != 0)
159 {
160 console.log('HResult=' + hr);
161 throw ('Resize returned HRESULT: ' + hr);
162 }
163 console.log('SUCCESS');
164 };
165
166 ds.terminal = ret;
167 ds._rpbuf = GM.CreateVariable(4096);
168 ds._rpbufRead = GM.CreateVariable(4);
169 ds.__read = function __read()
170 {
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 ()
174 {
175 var len = this.parent._rpbufRead.toBuffer().readUInt32LE();
176 if (len <= 0) { return; }
177
178 this.parent.push(this.parent._rpbuf.toBuffer().slice(0, len));
179 this.parent.__read();
180 });
181 this._rp.parent = this;
182 };
183 ds.__read();
184 return (ds);
185 }
186 else
187 {
188 }
189 }
190
191 }
192 throw ('Internal Error');
193 }
194
195 // This evaluates whether or not the powershell binary exists
196 this.PowerShellCapable = function ()
197 {
198 if (require('os').arch() == 'x64')
199 {
200 return (require('fs').existsSync(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe'));
201 }
202 else
203 {
204 return (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'));
205 }
206 }
207
208 // Start the PseudoConsole with the Command Prompt
209 this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
210 {
211 return (this.Create(process.env['windir'] + '\\System32\\cmd.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
212 }
213
214 // Start the PseduoConsole with PowerShell
215 this.StartPowerShell = function StartPowerShell(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
216 {
217 if (require('os').arch() == 'x64')
218 {
219 if (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'))
220 {
221 return (this.Create(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
222 }
223 else
224 {
225 return (this.Create(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
226 }
227 }
228 else
229 {
230 return (this.Create(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
231 }
232 }
233}
234
235if (process.platform == 'win32')
236{
237 module.exports = new vt();
238}