WSL run linux from windows without spawning a cmd-window

15.3k views Asked by At

I have WSL bash running in a cmd. I don't use it for anything, it just hangs there to keep the WSL system alive.

When I start X applications:

bash -c "DISPLAY=:0 xmessage hello &"

I get this result:

enter image description here

I can close down the command window without any problems, but it's rather annoying.

How can run commands without getting this cmd window every time?

6

There are 6 answers

0
mklement0 On BEST ANSWER

Here's a simpler solution, which, however, requires a WSH-based helper script, runHidden.vbs (see bottom section):

wscript .\runHidden.vbs bash -c "DISPLAY=:0 xmessage 'hello, world'"

To apply @davv's own launch-in-background technique to avoid creating a new bash instance every time:

One-time action (e.g., at boot time): launch a hidden, stay-open bash window. This spawns 2 bash processes: the Windows bash.exe process that owns the console window, and the WSL bash process (owned by the WSL init singleton), which is then available for servicing background commands.

wscript .\runHidden.vbs bash # hidden helper instance for servicing background commands

For every X Window-launching command: Terminate each command with & to have it be run by the hidden WSL bash instance asynchronously, without keeping the invoking bash instance alive:

wscript .\runHidden.vbs bash -c "DISPLAY=:0 xmessage 'hello, world' &"

runHidden.vbs source code:

' Simple command-line help.
select case WScript.Arguments(0)
case "-?", "/?", "-h", "--help"
  WScript.echo "Usage: runHidden executable [...]" & vbNewLine & vbNewLine & "Runs the specified command hidden (without a visible window)."
  WScript.Quit(0)
end select

' Separate the arguments into the executable name
' and a single string containing all arguments.
exe = WScript.Arguments(0)
sep = ""
for i = 1 to WScript.Arguments.Count -1
  ' Enclose arguments in "..." to preserve their original partitioning, if necessary.
  if Instr(WScript.Arguments(i), " ") > 0 then
    args = args & sep & """" & WScript.Arguments(i) & """"
  else
    args = args & sep & WScript.Arguments(i)
  end if
  sep = " "
next

' Execute the command with its window *hidden* (0)
WScript.CreateObject("Shell.Application").ShellExecute exe, args, "", "open", 0

Even when launched from a GUI app (such as via the Run dialog invoked with Win+R), this will not show a console window.

If your system is configured to execute .vbs scripts with wscript.exe by default (wscript //h:wscript /s, which, I think, is the default configuration), you can invoke runHidden.vbs directly, and if you put it in your %PATH%, by filename (root) only: runHidden ....

Note that use of the script is not limited to console applications: even GUI applications can be run hidden with it.

1
davvs On

So I just made this workaround for now. I really hope that there's a better way than this, but here it goes:

In the command prompt that lives purely to keep WSL alive, I have this script running:

wsl_run_server

#!/bin/bash
set -e
nc -kl 127.0.0.1 15150 | sh

And then I have this command to execute commands in background:

wsl_run_command

if ! pidof -x bin/wsl_run_server; then
    echo wsl_run_server isnt running!
    exit 1
fi
echo \($@\) \& | nc localhost 15150

from windows I then call:

bash -c "DISPLAY=:0 ~/bin/wsl_run_command xmessage hello"
0
Pedro Lobito On

Very later answer, but here's a python solution to run wsl commands without spawning a cmd-window :

import subprocess

cmd = sys.argv[1]
subprocess.getoutput(f'wsl {cmd}')

Usage:

pythonw scriptname.py 'command here'


Notes:

  • pythonw.exe is an executable file that belongs to Python. This process runs graphical interface applications without launching a system shell.
  • If python.exe is on your windows path, pythonw.exe should also be.
  • Make sure to quote the wsl command
0
XQ DD On

run command background

screen -dmS [name] [command]

example

screen -dmS gui bash -c "DISPLAY=:0 xmessage hello"

create a shortcut on windows desktop(run in wsl)

wslusc screen -dmS gui bash -c "DISPLAY=:0 xmessage hello"

0
D. Charles Pyle On

There no longer is a need to have that command window pop up anymore with WSLg recently added to the mix. You just can call bash using wslg.exe, like so (I use Ubuntu currently in WSL):

wslg ~ -d Ubuntu bash

This will create a BASH session that will just sit there without being seen. Alternatively, you can do what I do and run a few services that stay running. I created a script that checks for running services, and if it doesn't find them running, will run them. Create the file in /usr/bin:

sudo touch /usr/bin/start-wsl-services

sudo nano /usr/bin/start-wsl-services

Paste the following into the file:

#!/bin/bash

# Check for and run System-wide DBus service.
SERVICE="dbus-daemon"
if pgrep -x "$SERVICE" >/dev/null
then
    pgrep -a "$SERVICE"
else
    sudo /etc/init.d/dbus start
    pgrep -a "$SERVICE"
fi

# Check for and run CUPS Printing Service.
SERVICE="cupsd"
if pgrep -x "$SERVICE" >/dev/null
then
    pgrep -a "$SERVICE"
else
    sudo /etc/init.d/cups start
    pgrep -a "$SERVICE"
fi

# Check for and start Freshclam CLAMAV Update service.
SERVICE="freshclam"
if pgrep -x "$SERVICE" >/dev/null
then
    pgrep -a "$SERVICE"
else
    sudo /etc/init.d/clamav-freshclam start
    pgrep -a "$SERVICE"
fi

# Check for and start SANED Scanner service.
SERVICE="saned"
if pgrep -x "$SERVICE" >/dev/null
then
    pgrep -a "$SERVICE"
else
    sudo /etc/init.d/saned start
    pgrep -a "$SERVICE"
fi

# Check for and start screen-cleanup service.
SERVICE="screen-cleanup"
if pgrep -x "$SERVICE" >/dev/null
then
    pgrep -a "$SERVICE"
else
    sudo /etc/init.d/screen-cleanup start
    pgrep -a "$SERVICE"
fi

# Check for and start Preload service.
SERVICE="preload"
if pgrep -x "$SERVICE" >/dev/null
then
    pgrep -a "$SERVICE"
else
    sudo /etc/init.d/preload start
    pgrep -a "$SERVICE"
fi

# Prestart LibreOffice twice for faster loading.
#/usr/bin/libreoffice --terminate_after_init
#sleep 5
#/usr/bin/libreoffice --terminate_after_init

# Check for error, make sure all functions called and run, and pass the result on to calling process.
if [[ $? -ne 0 ]] ; then
    exit 1
else
    exit 0
fi

Save and exit the file, and then make it executable:

sudo chmod +x /usr/bin/start-wsl-services

I then call this using a shortcut that runs a startup script at startup. Or you can just run it manually. The command I use in the startup script is:

C:\Windows\System32\wslg.exe -d Ubuntu -- /usr/bin/start-wsl-services

The startup command script I use (named StartWSL.cmd) is as follows:

@echo off

echo Starting WSL Linux...

:RETRY

C:\Windows\System32\wslg.exe -d Ubuntu -- /usr/bin/start-wsl-services

REM - C:\Windows\System32\bash.exe -c '/usr/bin/start-wsl-services'

IF %ERRORLEVEL% NEQ 0 (GOTO RETRY)

REM - Allow time to see all results.
timeout /t 5 /nobreak >NUL

REM - Uncomment below line for troubleshooting.
REM - pause

exit 0

And that's how I now keep WSL running in the background on Windows 11, and similar to how I did it on Windows 10.

2
lorenz On

There's another simple solution, it requires an external executable though. It has no dependencies and was recommended by aseering on GitHub.

you can launch bash via run.exe: run.exe bash.exe -c "<whatever Linux command>". (run.exe is available here: http://www.straightrunning.com/projectrun/ , make sure you download the 64-bit version, the 32-bit version will not be able to find or run bash).

With run on the search PATH, you can just call

run bash -c "DISPLAY=:0 xmessage hello"