无法从PowerShell运行WSL内部命令。

huangapple go评论70阅读模式
英文:

Failure to run WSL internal command from powershell

问题

我需要在用户登录时通过任务计划程序触发的PowerShell脚本中运行WSL内部命令。操作系统:Windows 11 22H2。

```powershell
# 等待wsl.exe出现
# 在添加这行之前,我一直在收到关于找不到wsl命令的错误
# 可能与Windows中的MSIX包的工作方式有关
while (-Not (Get-Command wsl.exe))
{
    Start-Sleep -Milliseconds 500
}

while (-Not (wsl.exe ip a))
{
    Write-Output $LastExitCode
    Start-Sleep -Milliseconds 500
}

脚本日志中的结果:

1
1
1
...

在登录后,脚本会无限运行。WSL命令是可用的,我可以在控制台中输入 wsl.exe ip a 并获得结果,$LastExitCode 是0。但是脚本仍然在运行,直到我在任务计划程序中停止任务。不知何故,脚本无法访问WSL。

当我从管理员控制台运行脚本时,它可以正常工作。

任务计划程序中的任务选项:

  • 在运行任务时,使用以下用户帐户:<我的帐户>
  • (√) 仅当用户已登录时运行
  • [x] 以最高权限运行
  • 触发器:在登录时:<我的帐户>
  • 操作:启动程序:powershell.exe -file "<脚本路径>"

“以最高权限运行”是必需的,因为脚本需要运行 netsh 命令。

如何修复以运行WSL命令?

在Windows 10中,相同的脚本和相同的任务计划程序设置可以正常工作。


<details>
<summary>英文:</summary>

I need to run WSL internal command from powershell script triggered by task scheduler on user logon. OS: Windows 11 22H2. 

```powershell
# 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:

  • When running the task, use the following user account: &lt;my account&gt;
  • (*) Run only when the user is logged on
  • [x] Run with highest privileges
  • Triggers: at log on: &lt;my account&gt;
  • Actions: Start a program: powershell.exe -file "&lt;path to the script&gt;"

"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.

答案1

得分: 1

这似乎是一个已知问题,截至到本文撰写时 - 请参阅GitHub问题#9231,它影响以下场景:

  • 您的WSL版本是从Microsoft Store安装的(可以通过应用程序或winget.exe安装)。

  • 您试图从会话0,即隐藏的services窗口站点中启动wsl.exe

    • 这意味着您选择了一个涉及服务的登录方法,这适用于任务计划程序GUI(taskschd.msc)中选择了Run whether user is logged or not选项的情况(当您在特权系统帐户(如NT AUTHORITY\SYSTEM)的上下文中运行时也适用)。<sup>[1]</sup>

您自己找到了**解决方法**:

  • 使用选择了Run only when user is logged on选项运行您的任务,有两个选项:

    • 配置您的登录时运行任务为_特定用户_,即仅在该用户登录时运行。
    • 配置您的登录时运行任务,以便在_给定组_中的用户登录时运行,比如Users(以定位所有用户)。
      请注意,这假设组中的所有帐户都至少安装了一个WSL发行版,以其帐户的上下文为基础。
  • 无论Run with highest privileges是否选中,此解决方法都有效。

  • 此解决方法的代价是您的任务在登录时必定以_可见方式_运行(将打开一个控制台窗口,在任务退出时自动关闭)。


有了这个解决方法,我的非正式测试表明,您_不需要_等待wsl.exe可执行文件在您的脚本中变得可用(甚至第一个wsl.exe ip a调用也会成功);为了安全起见,在您的脚本的以下重制版中保留了一个循环 - 尽管只有一个:

do {
  # 注意: 
  # * 为了允许日志捕获*stderr*输出,添加2>&1  
  # * `catch`块仅在找不到`wsl.exe`时触发。
  #   使用退出代码`2`来表示这一事实,但您可以设置任何非零值。
  try { wsl.exe ip a } catch { $global:LASTEXITCODE = 2 }
  if ($LASTEXITCODE -eq 0) { break }
  $LASTEXITCODE   # 输出非零退出代码。
  Start-Sleep -Milliseconds 500
} while ($true)

注意:

  • 您_不应该_在尝试测试外部程序的进程_退出代码_时应用-not运算符/直接转换为布尔值:

    • 在PowerShell中,这样做会将命令的_输出_强制转换为布尔值($true$false),在外部程序的情况下,这适用于其_stdout输出_,并且只有在stdout输出_缺失_时才会被转换为$false(这将由-not反转为$true)。

    • 然而,您不能从stdout输出的存在或不存在推断出外部程序调用的成功状态:即使在最终进程退出代码信号失败(非零值)的情况下,可能也会有这样的输出,反之亦然,一个进程可能会_静默地_成功(0)(没有stdout输出):

      • 比如说:while (-not (wsl.exe true)) { } 在定义上是一个_无限循环_,因为true shell内置_仅_设置_退出代码_,而不生成_stdout输出_,因此-not (wsl.exe true)总是$true(实际上等同于-not $null)。
  • 确定外部程序调用的成功与否的唯一可靠方法是检查其进程_退出代码_,在PowerShell中,这需要自动的$LASTEXITCODE变量,如上所示。


<sup>[1] 看起来这并不严格是关于0会话本身的,因为使用以下内置系统帐户运行,这些帐户也在此会话中运行,不会_失败_,但会抱怨在这些帐户的上下文中没有安装发行版:LOCAL SERVICENETWORK SERVICE。在其他情况下,您会得到_错误_:尝试使用选择了Run whether user is logged or not的特定用户身份运行会导致错误The file cannot be accessed by the system;尝试使用内置的SYSTEM帐户运行会导致错误Access denied。</sup>

英文:

<!-- language-all: sh -->

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.

    • This, in turn, implies that you've chosen a logon method that involves a service, which applies whenever the 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).<sup>[1]</sup>

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:

    • Configure your run-at-logon task to run as a specific user, i.e. only when that user logs on.
    • Configure your run-at-logon task to run whenever a user from a given group logs on, say Users (to target all users).
      Note that this assumes that all accounts in the group have at least one WSL distro installed in the context of their account.
  • 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&gt;&amp;1  
  # * The `catch` block triggers only if `wsl.exe` can&#39;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):

      • Case in point: 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.


<sup>[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.</sup>

答案2

得分: 0

EDIT: 在此之前,在这个配置下它并不起作用,然后我来回切换了一下,现在可以了。我完全不知道为什么。

所以:如果任务同时具有以下设置,WSL可以成功地从计划任务中运行:
[x] “使用最高权限运行”
(*) 仅在用户已登录时运行

如果任务设置为:
(*) 无论用户是否已登录都运行

无论[x]“不存储密码”是开启还是关闭,任务都会失败。

英文:

EDIT: it did not work in this configuration before, then I switched it back and forth, and now it does. I don't have a slightest idea why.

So: WSL successfully runs from scheduled task with [x] "Run with highest privileges" if the task also has setting

(*) Run only when user is logged on

Described failure occurs if the task has setting

(*) Run whether the user is logged on or not

[x] Do not store password may be on or off, task still fails.

huangapple
  • 本文由 发表于 2023年2月27日 02:34:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75574200.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定