I need to run WSL internal command from powershell script triggered by task scheduler on user logon. OS: Windows 11 22H2.
# waiting for wsl.exe to appear
# before adding this I was getting errors about no command wsl
# probably something about how MSIX packages work in Windows
while (-Not (Get-Command wsl.exe))
{
Start-Sleep -Milliseconds 500
}
while (-Not (wsl.exe ip a))
{
Write-Output $LastExitCode
Start-Sleep -Milliseconds 500
}
Result in script logs is:
1
1
1
...
After logon the script runs infinitely. WSL commands are available, I can write in a console wsl.exe ip a
and get the result and $LastExitCode
is 0. But the script is still running until I stop the task in task scheduler. Somehow the script can not get access to WSL.
When I run the script from admin console, it works fine.
Task options in task scheduler:
<my account>
(*)
Run only when the user is logged on[x]
Run with highest privileges<my account>
<path to the script>
""Run with highest privileges" is necessary because the script needs to run netsh
commands.
How can I fix it to run WSL command?
In Windows 10 the same script with the same task scheduler settings works fine.
This appears to be a known problem as of this writing - see GitHub issue #9231 - and it affects the following scenario:
Your WSL version was installed from the Microsoft Store (either via the application or via winget.exe
).
You're trying to launch wsl.exe
from session 0
, i.e. the hidden services
window station in which services run.
Run whether user is logged or not
option in the Task Scheduler GUI (taskschd.msc
) is selected (which also applies when you run in the context of a privileged system account such as NT AUTHORITY\SYSTEM
).[1]You've found the workaround yourself:
Run your task with the Run only when user is logged on
option selected, for which you have two options:
Users
(to target all users).The workaround is effective whether or not Run with highest privileges
is checked.
The price of this workaround is that your task invariably runs visibly at logon (a console window will open that auto-closes when the task exits).
With the workaround in place, my informal tests suggest that you do not need to wait for the wsl.exe
executable to become available in your script (and even the first wsl.exe ip a
call succeeds); to be safe, a loop - albeit just a single one - is retained in the following reformulation of your script:
do {
# Note:
# * To allow the logs to capture *stderr* output too, add 2>&1
# * The `catch` block triggers only if `wsl.exe` can't be found.
# Exit code `2` is used to signal that fact, but you can set any nonzero one.
try { wsl.exe ip a } catch { $global:LASTEXITCODE = 2 }
if ($LASTEXITCODE -eq 0) { break }
$LASTEXITCODE # Output the nonzero exit code.
Start-Sleep -Milliseconds 500
} while ($true)
Note:
You should not apply the -not
operator / direct to-Boolean coercion to external-program calls in an effort to test their process exit code:
In PowerShell, doing so coerces a command's output to a Boolean ($true
or $false
), which in the case of external programs applies to their stdout output, and only the absence of stdout output is coerced to $false
(which -not
inverts to $true
).
However, you cannot infer the success status of an external-program call from the presence or absence of stdout output: there may be such output even if the process exit code ultimately signals failure (nonzero value) and, conversely, a process may succeed (0
) quietly (without stdout output):
while (-not (wsl.exe true)) { }
is by definition an infinite loop, because the true
shell builtin only sets an exit code, without producing stdout output, so that -not (wsl.exe true)
is always $true
(it is in effect the same as -not $null
).The only reliable way to determine success vs. failure of an external-program call is to examine its process exit code, which in PowerShell requires the automatic $LASTEXITCODE
variable, as shown above.
[1] It seems that it isn't strictly about session 0
per se, because running with the following built-in system accounts, which also run in this session, doesn't fail, but complains about no distros being installed in the context of these accounts: LOCAL SERVICE
and NETWORK SERVICE
. In other cases you get errors: Trying to run as a a specific user with Run whether user is logged or not
selected, results in error The file cannot be accessed by the system
; trying to run with the built-in SYSTEM
account results in error Access denied
.