VirtualDesktop icon indicating copy to clipboard operation
VirtualDesktop copied to clipboard

This program always throws an error in programming

Open NabiKAZ opened this issue 6 months ago • 5 comments

This project seems to always throw an error when used in programming.

This is my simple code in nodejs:

import { exec } from 'child_process';
import util from 'util';
const execAsync = util.promisify(exec);

const { stdout, stderr } = await execAsync(`VirtualDesktop11.exe`);
console.log('result:', stdout, stderr);

which produces the following error:

PS C:\Nabi> node .\test.mjs
node:internal/errors:983
  const err = new Error(message);
              ^

Error: Command failed: VirtualDesktop11.exe

    at genericNodeError (node:internal/errors:983:15)
    at wrappedFn (node:internal/errors:537:14)
    at ChildProcess.exithandler (node:child_process:414:12)
    at ChildProcess.emit (node:events:518:28)
    at maybeClose (node:internal/child_process:1101:16)
    at ChildProcess._handle.onexit (node:internal/child_process:304:5) {
  code: 4294967294,
  killed: false,
  signal: null,
  cmd: 'VirtualDesktop11.exe',
  stdout: 'VirtualDesktop.exe\t\t\t\tMarkus Scholtes, 2025, v1.20\n' +
    '\r\n' +
    'Command line tool to manage the virtual desktops of Windows 11.\r\n' +
    'Parameters can be given as a sequence of commands. The result - most of the\r\n' +
    'times the number of the processed desktop - can be used as input for the next\r\n' +
    'parameter. The result of the last command is returned as error level.\r\n' +
    'Virtual desktop numbers start with 0.\n' +
    '\r\n' +
    'Parameters (leading / can be omitted or - can be used instead):\n' +
    '\r\n' +
    '/Help /h /?      this help screen.\r\n' +
    '/Verbose /Quiet  enable verbose (default) or quiet mode (short: /v and /q).\r\n' +
    '/Break /Continue break (default) or continue on error (short: /b and /co).\r\n' +
    "/Animation:<s>   Enable switch animations (default) with 'On' or '1' and\r\n" +
    "                   disable switch animations with 'Off' or '0' (short: /anim).\r\n" +
    '/List            list all virtual desktops (short: /li).\r\n' +
    '/Count           get count of virtual desktops to pipeline (short: /c).\r\n' +
    '/GetDesktop:<n|s> get number of virtual desktop <n> or desktop with text <s> in\r\n' +
    '                   name to pipeline (short: /gd).\r\n' +
    '/GetCurrentDesktop  get number of current desktop to pipeline (short: /gcd).\r\n' +
    '/Name[:<s>]      set name of desktop with number in pipeline (short: /na).\r\n' +
    '/Wallpaper[:<s>] set wallpaper path of desktop with number in pipeline (short:\r\n' +
    '                   /wp).\r\n' +
    '/AllWallpapers:<s> set wallpaper path of all desktops (short: /awp).\r\n' +
    '/IsVisible[:<n|s>] is desktop number <n>, desktop with text <s> in name or with\r\n' +
    '                   number in pipeline visible (short: /iv)? Returns 0 for\r\n' +
    '                   visible and 1 for invisible.\r\n' +
    '/Switch[:<n|s>]  switch to desktop with number <n>, desktop with text <s> in\r\n' +
    '                   name or with number in pipeline (short: /s).\r\n' +
    '/Left            switch to virtual desktop to the left of the active desktop\r\n' +
    '                   (short: /l).\r\n' +
    '/Right           switch to virtual desktop to the right of the active desktop\r\n' +
    '                   (short: /ri).\r\n' +
    '/Wrap /NoWrap    /Left or /Right switch over or generate an error when the edge\r\n' +
    '                   is reached (default)(short /w and /nw).\r\n' +
    '/New             create new desktop (short: /n). Number is stored in pipeline.\r\n' +
    '/Remove[:<n|s>]  remove desktop number <n>, desktop with text <s> in name or\r\n' +
    '                   desktop with number in pipeline (short: /r).\r\n' +
    '/RemoveAll       remove all desktops but visible (short: /ra).\r\n' +
    '/SwapDesktop:<n|s>  swap desktop in pipeline with desktop number <n> or desktop\r\n' +
    '                   with text <s> in name (short: /sd).\r\n' +
    '/MoveDesktop:<n|s>  move desktop in pipeline to desktop number <n> or desktop\r\n' +
    '                   with text <s> in name (short: /md).\r\n' +
    '/MoveWindowsToDesktop:<n|s>  move windows on desktop in pipeline to desktop\r\n' +
    '                   number <n> or desktop with text <s> in name (short: /mwtd).\r\n' +
    '/MoveWindow:<s|n>  move process with name <s> or id <n> to desktop with number\r\n' +
    '                   in pipeline (short: /mw).\r\n' +
    '/MoveWindowHandle:<s|n>  move window with text <s> in title or handle <n> to\r\n' +
    '                   desktop with number in pipeline (short: /mwh).\r\n' +
    '/MoveActiveWindow  move active window to desktop with number in pipeline\r\n' +
    '                   (short: /maw).\r\n' +
    '/GetDesktopFromWindow:<s|n>  get desktop number where process with name <s> or\r\n' +
    '                   id <n> is displayed (short: /gdfw).\r\n' +
    '/GetDesktopFromWindowHandle:<s|n>  get desktop number where window with text\r\n' +
    '                   <s> in title or handle <n> is displayed (short: /gdfwh).\r\n' +
    '/IsWindowOnDesktop:<s|n>  check if process with name <s> or id <n> is on\r\n' +
    '                   desktop with number in pipeline (short: /iwod). Returns 0\r\n' +
    '                   for yes, 1 for no.\r\n' +
    '/IsWindowHandleOnDesktop:<s|n>  check if window with text <s> in title or\r\n' +
    '                   handle <n> is on desktop with number in pipeline\r\n' +
    '                   (short: /iwhod). Returns 0 for yes, 1 for no.\r\n' +
    '/ListWindowsOnDesktop[:<n|s>]  list handles of windows on desktop number <n>,\r\n' +
    '                   desktop with text <s> in name or desktop with number in\r\n' +
    '                   pipeline (short: /lwod).\r\n' +
    '/CloseWindowsOnDesktop[:<n|s>]  close windows on desktop number <n>, desktop\r\n' +
    '                   with text <s> in name or desktop with number in pipeline\r\n' +
    '                   (short: /cwod).\r\n' +
    '/PinWindow:<s|n>  pin process with name <s> or id <n> to all desktops\r\n' +
    '                   (short: /pw).\r\n' +
    '/PinActiveWindow  pin active window to all desktops (short: /paw).\r\n' +
    '/PinWindowHandle:<s|n>  pin window with text <s> in title or handle <n> to all\r\n' +
    '                   desktops (short: /pwh).\r\n' +
    '/UnPinWindow:<s|n>  unpin process with name <s> or id <n> from all desktops\r\n' +
    '                   (short: /upw).\r\n' +
    '/UnPinActiveWindow  unpin active window from all desktops (short: /upaw).\r\n' +
    '/UnPinWindowHandle:<s|n>  unpin window with text <s> in title or handle <n>\r\n' +
    '                   from all desktops (short: /upwh).\r\n' +
    '/IsWindowPinned:<s|n>  check if process with name <s> or id <n> is pinned to\r\n' +
    '                   all desktops (short: /iwp). Returns 0 for yes, 1 for no.\r\n' +
    '/IsWindowHandlePinned:<s|n>  check if window with text <s> in title or handle\r\n' +
    '                   <n> is pinned to all desktops (short: /iwhp). Returns 0 for\r\n' +
    '                   yes, 1 for no.\r\n' +
    '/PinApplication:<s|n>  pin application with name <s> or id <n> to all desktops\r\n' +
    '                   (short: /pa).\r\n' +
    '/UnPinApplication:<s|n>  unpin application with name <s> or id <n> from all\r\n' +
    '                   desktops (short: /upa).\r\n' +
    '/IsApplicationPinned:<s|n>  check if application with name <s> or id <n> is\r\n' +
    '                   pinned to all desktops (short: /iap). Returns 0 for yes, 1\r\n' +
    '                   for no.\r\n' +
    '/Calc:<n>        add <n> to result, negative values are allowed (short: /ca).\r\n' +
    '/WaitKey         wait for key press (short: /wk).\r\n' +
    '/Sleep:<n>       wait for <n> milliseconds (short: /sl).\n' +
    '\r\n' +
    'Hint: Instead of a desktop name you can use LAST or *LAST* to select the last\r\n' +
    'virtual desktop.\r\n' +
    'Hint: Insert ^^ somewhere in window title parameters to prevent finding the own\r\n' +
    'window. ^ is removed before searching window titles.\n' +
    '\r\n' +
    'Examples:\r\n' +
    'Virtualdesktop.exe /LIST\r\n' +
    'Virtualdesktop.exe "-Switch:Desktop 2"\r\n' +
    'Virtualdesktop.exe -New -Switch -GetCurrentDesktop\r\n' +
    'Virtualdesktop.exe Q N /MOVEACTIVEWINDOW /SWITCH\r\n' +
    'Virtualdesktop.exe sleep:200 gd:1 mw:notepad s\r\n' +
    'Virtualdesktop.exe /Count /continue /Remove /Remove /Count\r\n' +
    'Virtualdesktop.exe /Count /Calc:-1 /Switch\r\n' +
    'VirtualDesktop.exe -IsWindowPinned:cmd\r\n' +
    'if ERRORLEVEL 1 VirtualDesktop.exe PinWindow:cmd\r\n' +
    'Virtualdesktop.exe -GetDesktop:*last* "-MoveWindowHandle:note^^pad"\r\n',
  stderr: ''
}

Node.js v22.14.0

While the point is that the code is executed correctly and this is clear from the output before!

Anyway I tried to handle it with try/catch:

try {
  const { stdout, stderr } = await execAsync(`VirtualDesktop11.exe`);
  console.log('result:', stdout, stderr);
} catch (error) {
  console.error('ERROR:', error.message);
}

But this always returns the following error even when the command is executed correctly:

ERROR: Command failed: VirtualDesktop11.exe

I noticed that the error object in the previous code also contains stdout. So I have to do it in a catch block, which makes the code dirty and unprincipled. Something like this:

try {
  await execAsync(`VirtualDesktop11.exe`);
} catch (error) {
  if (error.stderr) {
    console.log("stderr from execAsync:", error.stderr);
  }
  if (error.stdout) {
    console.log("stdout from execAsync:", error.stdout);
  }
}

It seems to me that my code should not encounter stderr when we have no errors and only stdout. Maybe this is a bug in the code structure. For example, maybe stderr: "" should not be generated or at some other level it is causing the library I am using to fail and generate the error.

NabiKAZ avatar May 12 '25 01:05 NabiKAZ

Hello @NabiKAZ,

calling Virtualdesktop.exe without parameter is detected as an error and returns ERRORLEVEL 2. If you want to show the help text please use Virtualdesktop.exe /?, Virtualdesktop.exe /h or Virtualdesktop.exe /help as indicated from the help text, then no error is generated.

Greetings

Markus

MScholtes avatar May 12 '25 15:05 MScholtes

Thanks. Ok, it worked fine for /help or /list. But when I set /MoveWindowHandle it still gives an error. While this command works correctly on the command line. Even more interesting is that even though the code throws an error, it executes correctly. That is, despite the stderr error, my window is moved correctly! That is why I said in the beginning that it always throws an error.

Code example 1 and its output:

import { exec } from 'child_process';
import util from 'util';
const execAsync = util.promisify(exec);

try {
    const title = 'Calculator';
    const titleModified = title[0] + "^^" + title.slice(1);
    const result = await execAsync(`C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"${titleModified}"`);
    console.log('result:', result);
} catch (error) {
    console.log('ERROR:', error);
}
ERROR: Error: Command failed: C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"C^^alculator"

    at genericNodeError (node:internal/errors:983:15)
    at wrappedFn (node:internal/errors:537:14)
    at ChildProcess.exithandler (node:child_process:414:12)
    at ChildProcess.emit (node:events:518:28)
    at maybeClose (node:internal/child_process:1101:16)
    at ChildProcess._handle.onexit (node:internal/child_process:304:5) {
  code: 1,
  killed: false,
  signal: null,
  cmd: 'C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"C^^alculator"',
  stdout: "Virtual desktop number 1 (desktop 'Desktop 2') selected\r\n" +
    "Window 'Calculator' moved to desktop number 1 (desktop 'Desktop 2')\r\n",
  stderr: ''
}

I thought it might be related to ^^ but no: Code example 2 and its output:

import { exec } from 'child_process';
import util from 'util';
const execAsync = util.promisify(exec);

try {
    const title = 'Calculator';
    const result = await execAsync(`C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"${title}"`);
    console.log('result:', result);
} catch (error) {
    console.log('ERROR:', error);
}
ERROR: Error: Command failed: C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"Calculator"

    at genericNodeError (node:internal/errors:983:15)
    at wrappedFn (node:internal/errors:537:14)
    at ChildProcess.exithandler (node:child_process:414:12)
    at ChildProcess.emit (node:events:518:28)
    at maybeClose (node:internal/child_process:1101:16)
    at ChildProcess._handle.onexit (node:internal/child_process:304:5) {
  code: 1,
  killed: false,
  signal: null,
  cmd: 'C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"Calculator"',
  stdout: "Virtual desktop number 1 (desktop 'Desktop 2') selected\r\n" +
    "Window 'Calculator' moved to desktop number 1 (desktop 'Desktop 2')\r\n",
  stderr: ''
}

NabiKAZ avatar May 12 '25 23:05 NabiKAZ

Hello @NabiKAZ,

I think I understand the issue now. But the real challenge here is that I intentionally set the last result as error level so that it can be reused in scripts, and this conflicts with your usage. I think only a trick can help here: always set /GetDesktop:0 as last parameter for VirtualDesktop.exe.

This selects desktop 0 and so 0 ist returned by the program. And 0 means no error. And since no action is provided, the parameter will do nothing.

Hope this helps,

greetings

Markus

MScholtes avatar May 13 '25 17:05 MScholtes

Thanks, this seems to be working fine:

const result = await execAsync(`C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"${titleModified}" /GetDesktop:0`);


result: {
  stdout: "Virtual desktop number 1 (desktop 'Desktop 2') selected\r\n" +
    "Window 'Calculator' moved to desktop number 1 (desktop 'Desktop 2')\r\n" +
    "Virtual desktop number 0 (desktop 'Desktop 1') selected\r\n",
  stderr: ''
}

But this is a question for me why the command line does not produce an error but the script sees it as an error (even stderr is empty)

PS C:\> C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"C^^alculator"
Virtual desktop number 1 (desktop 'Desktop 2') selected
Window 'Calculator' moved to desktop number 1 (desktop 'Desktop 2')

PS C:\> C:/Users/Nabi/Downloads/VirtualDesktop11.exe /GetDesktop:*last* /MoveWindowHandle:"C^^alculator" /GetDesktop:0
Virtual desktop number 1 (desktop 'Desktop 2') selected
Window 'Calculator' moved to desktop number 1 (desktop 'Desktop 2')
Virtual desktop number 0 (desktop 'Desktop 1') selected

NabiKAZ avatar May 14 '25 08:05 NabiKAZ

Hello @NabiKAZ,

there's an very old convention on Unix, CP/M, DOS, Z/OS etc. that an exit code or return code of 0 for command line tools means success, other values mean error. The internet has a lot to say to it.

Greetings

Markus

MScholtes avatar May 14 '25 17:05 MScholtes

Closed for no reaction - think issue is solved

MScholtes avatar Jul 10 '25 14:07 MScholtes