win-virtual-terminal.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. Copyright 2019 Intel Corporation
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. var PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
  14. var EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
  15. var HEAP_ZERO_MEMORY = 0x00000008;
  16. var duplex = require('stream').Duplex;
  17. function vt()
  18. {
  19. this._ObjectID = 'win-virtual-terminal';
  20. Object.defineProperty(this, 'supported', {
  21. value: (function ()
  22. {
  23. var gm = require('_GenericMarshal');
  24. var k32 = gm.CreateNativeProxy('kernel32.dll');
  25. try
  26. {
  27. k32.CreateMethod('CreatePseudoConsole');
  28. }
  29. catch(e)
  30. {
  31. return (false);
  32. }
  33. return (true);
  34. })()
  35. });
  36. this.Create = function Create(path, width, height)
  37. {
  38. if (!this.supported) { throw ('This build of Windows does not have support for PseudoConsoles'); }
  39. if (!width) { width = 80; }
  40. if (!height) { height = 25; }
  41. var GM = require('_GenericMarshal');
  42. var k32 = GM.CreateNativeProxy('kernel32.dll');
  43. k32.CreateMethod('CancelIoEx'); // https://learn.microsoft.com/en-us/windows/win32/fileio/cancelioex-func
  44. k32.CreateMethod('CreatePipe'); // https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
  45. k32.CreateMethod('CreateProcessW'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
  46. k32.CreateMethod('CreatePseudoConsole'); // https://learn.microsoft.com/en-us/windows/console/createpseudoconsole
  47. k32.CreateMethod('CloseHandle'); // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
  48. k32.CreateMethod('ClosePseudoConsole'); // https://learn.microsoft.com/en-us/windows/console/closepseudoconsole
  49. k32.CreateMethod('GetProcessHeap'); // https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-getprocessheap
  50. k32.CreateMethod('HeapAlloc'); // https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc
  51. k32.CreateMethod('InitializeProcThreadAttributeList'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist
  52. k32.CreateMethod('ResizePseudoConsole'); // https://learn.microsoft.com/en-us/windows/console/resizepseudoconsole
  53. k32.CreateMethod('UpdateProcThreadAttribute'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute
  54. k32.CreateMethod('WriteFile'); // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
  55. k32.CreateMethod('ReadFile'); // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
  56. k32.CreateMethod('TerminateProcess'); // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess
  57. var ret = { _h: GM.CreatePointer(), _consoleInput: GM.CreatePointer(), _consoleOutput: GM.CreatePointer(), _input: GM.CreatePointer(), _output: GM.CreatePointer(), k32: k32 };
  58. var attrSize = GM.CreateVariable(8);
  59. var attrList;
  60. var pi = GM.CreateVariable(GM.PointerSize == 4 ? 16 : 24);
  61. // Create the necessary pipes
  62. if (k32.CreatePipe(ret._consoleInput, ret._input, 0, 0).Val == 0) { console.log('PIPE/FAIL'); }
  63. if (k32.CreatePipe(ret._output, ret._consoleOutput, 0, 0).Val == 0) { console.log('PIPE/FAIL'); }
  64. if (k32.CreatePseudoConsole((height << 16) | width, ret._consoleInput.Deref(), ret._consoleOutput.Deref(), 0, ret._h).Val != 0)
  65. {
  66. throw ('Error calling CreatePseudoConsole()');
  67. }
  68. //
  69. // Reference for STARTUPINFOEXW
  70. // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-startupinfoexw
  71. //
  72. //
  73. // Reference for STARTUPINFOW
  74. // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow
  75. //
  76. k32.InitializeProcThreadAttributeList(0, 1, 0, attrSize);
  77. attrList = GM.CreateVariable(attrSize.toBuffer().readUInt32LE());
  78. var startupinfoex = GM.CreateVariable(GM.PointerSize == 8 ? 112 : 72); // Create Structure, 64 bits is 112 bytes, 32 bits is 72 bytes
  79. startupinfoex.toBuffer().writeUInt32LE(GM.PointerSize == 8 ? 112 : 72, 0); // Write buffer size
  80. attrList.pointerBuffer().copy(startupinfoex.Deref(GM.PointerSize == 8 ? 104 : 68, GM.PointerSize).toBuffer()); // Write the reference to STARTUPINFOEX
  81. if (k32.InitializeProcThreadAttributeList(attrList, 1, 0, attrSize).Val != 0)
  82. {
  83. if (k32.UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, ret._h.Deref(), GM.PointerSize, 0, 0).Val != 0)
  84. {
  85. 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
  86. {
  87. //
  88. // Create a Stream Object, to be able to read/write data to the pseudoconsole
  89. //
  90. ret._startupinfoex = startupinfoex;
  91. ret._process = pi.Deref(0);
  92. ret._pid = pi.Deref(GM.PointerSize == 4 ? 8 : 16, 4).toBuffer().readUInt32LE();
  93. var ds = new duplex(
  94. {
  95. 'write': function (chunk, flush)
  96. {
  97. var written = require('_GenericMarshal').CreateVariable(4);
  98. this.terminal.k32.WriteFile(this.terminal._input.Deref(), require('_GenericMarshal').CreateVariable(chunk), chunk.length, written, 0);
  99. flush();
  100. return (true);
  101. },
  102. 'final': function (flush)
  103. {
  104. if (this.terminal._process)
  105. {
  106. this.terminal._process = null;
  107. k32.ClosePseudoConsole(this._obj._h.Deref());
  108. }
  109. flush();
  110. }
  111. });
  112. //
  113. // The ProcessInfo object is signaled when the process exits
  114. //
  115. ds._obj = ret;
  116. ret._waiter = require('DescriptorEvents').addDescriptor(pi.Deref(0));
  117. ret._waiter.ds = ds;
  118. ret._waiter._obj = ret;
  119. ret._waiter.on('signaled', function ()
  120. {
  121. k32.CancelIoEx(this._obj._output.Deref(), 0);
  122. // Child process has exited
  123. this.ds.push(null);
  124. if (this._obj._process)
  125. {
  126. this._obj._process = null;
  127. k32.ClosePseudoConsole(this._obj._h.Deref());
  128. }
  129. k32.CloseHandle(this._obj._input.Deref());
  130. k32.CloseHandle(this._obj._output.Deref());
  131. k32.CloseHandle(this._obj._consoleInput.Deref());
  132. k32.CloseHandle(this._obj._consoleOutput.Deref());
  133. });
  134. ds.resizeTerminal = function (w, h)
  135. {
  136. console.setDestination(console.Destinations.LOGFILE);
  137. console.log('resizeTerminal(' + w + ', ' + h + ')');
  138. var hr;
  139. if((hr=k32.ResizePseudoConsole(this._obj._h.Deref(), (h << 16) | w).Val) != 0)
  140. {
  141. console.log('HResult=' + hr);
  142. throw ('Resize returned HRESULT: ' + hr);
  143. }
  144. console.log('SUCCESS');
  145. };
  146. ds.terminal = ret;
  147. ds._rpbuf = GM.CreateVariable(4096);
  148. ds._rpbufRead = GM.CreateVariable(4);
  149. ds.__read = function __read()
  150. {
  151. // Asyncronously read data from the pseudoconsole
  152. this._rp = this.terminal.k32.ReadFile.async(this.terminal._output.Deref(), this._rpbuf, this._rpbuf._size, this._rpbufRead, 0);
  153. this._rp.then(function ()
  154. {
  155. var len = this.parent._rpbufRead.toBuffer().readUInt32LE();
  156. if (len <= 0) { return; }
  157. this.parent.push(this.parent._rpbuf.toBuffer().slice(0, len));
  158. this.parent.__read();
  159. });
  160. this._rp.parent = this;
  161. };
  162. ds.__read();
  163. return (ds);
  164. }
  165. else
  166. {
  167. }
  168. }
  169. }
  170. throw ('Internal Error');
  171. }
  172. // This evaluates whether or not the powershell binary exists
  173. this.PowerShellCapable = function ()
  174. {
  175. if (require('os').arch() == 'x64')
  176. {
  177. return (require('fs').existsSync(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe'));
  178. }
  179. else
  180. {
  181. return (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'));
  182. }
  183. }
  184. // Start the PseudoConsole with the Command Prompt
  185. this.Start = function Start(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
  186. {
  187. return (this.Create(process.env['windir'] + '\\System32\\cmd.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
  188. }
  189. // Start the PseduoConsole with PowerShell
  190. this.StartPowerShell = function StartPowerShell(CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT)
  191. {
  192. if (require('os').arch() == 'x64')
  193. {
  194. if (require('fs').existsSync(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'))
  195. {
  196. return (this.Create(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
  197. }
  198. else
  199. {
  200. return (this.Create(process.env['windir'] + '\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
  201. }
  202. }
  203. else
  204. {
  205. return (this.Create(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', CONSOLE_SCREEN_WIDTH, CONSOLE_SCREEN_HEIGHT));
  206. }
  207. }
  208. }
  209. if (process.platform == 'win32')
  210. {
  211. module.exports = new vt();
  212. }