通过PowerShell以提升的权限调用批处理文件并检索退出代码

3
我的Windows批处理文件应该由用户 不带 管理员权限启动。在某个步骤中,它应该使用提升的权限来调用自己。我已经学习到可以使用PowerShell的runas功能实现这一点(batch.bat ⭢ PowerShell ⭢ batch.bat)。这个方法非常有效。
不幸的是,我无法从具有提升权限的批处理执行中接收退出代码。即使没有任何错误消息,我始终得到 1。我不知道退出代码在哪个阶段丢失了,是第一步(批处理返回给PowerShell)还是第二步(PowerShell返回给批处理)。
我相信,我已经尝试了所有类似问题的大量建议答案,但显然无法解决问题。我需要建议。
MVE 应该指示提升的批处理返回0
@echo off
echo param=%~1
openfiles /local >nul 2>&1

IF %ERRORLEVEL% EQU 0 (
    echo elevated, exit 0
    pause
    exit 0
) ELSE (
    echo not elevated. trying to elevate.
    powershell start-process -wait -verb runas '%0' -argumentlist /foo
    echo powershell returned %errorlevel%.
)

需要注意的是(已编辑以消除误解):虽然由用户进行的非提升调用不需要任何参数,但提升调用会引入一个额外的参数“/foo”。这让我感到更加困难,因为我没有找到不失去此参数的解决方案。不过,这似乎是一个相当不寻常的用例。

2个回答

2
为了解决"argument problem",你可以使用以下方法:
powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
退出代码问题:
第一个问题是这行代码:

echo powershell returned %errorlevel%.

这是不可行的,因为它在代码块内部,%errorlevel%将在调用powershell之前被扩展,因此它始终是1 - openfiles /local ...的结果。
但即使延迟扩展,我始终得到0,可能是因为它是成功的runas的退出代码,它能够启动您的批处理程序。
您可以使用一个解决方案并将exitcode存储在临时文件中。
@echo off
setlocal EnableDelayedExpansion
echo param=%*
openfiles /local >nul 2>&1

IF %ERRORLEVEL% EQU 0 (
    echo elevated, exit 13
    pause
    echo 13 > "%temp%\_exitcode.tmp"
    rem *** using 14 here to show that it doesn't be stored in errorlevel
    exit 14
) ELSE (
    echo not elevated. trying to elevate.
    powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
    set /p _exitcode= < "%temp%\_exitcode.tmp"
    del "%temp%\_exitcode.tmp"
    echo powershell returned !_exitcode!, lvl !errorlevel!.
)

抱歉。我删除了EnableDelayedExpansion以使示例“最小化”,因为“发现”它没有影响。我注意到我错了。接下来,我从您的答案和您在另一个问题中的帖子中学到了bbb(批处理初学者错误)并需要感叹号扩展。然而,我不喜欢提出的解决方案,因为对我来说似乎有些hacky,但是好吧。至少它起作用了,这很棒! - Twonky
@Twonky 你说得对,这只是一个权宜之计。我尝试过使用 powershell ... -PassThru 但结果并没有改善。 - jeb
_exitcode.tmp 移动到 %temp% 并使用 IF EXISTS 保护 set 命令。认为这个解决方案已经足够好了!谢谢,你太棒了! - Twonky

0

你没有将要执行的PowerShell命令放在引号中,最好使用完整路径并包括脚本的任何参数。为了通用性,可以将以下PowerShell调用复制到其他脚本中:

powershell -c "if([bool]'%*'){ Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList ('%*' -split '\s+') } else { Start-Process -Wait -Verb runas '%~dpnx0' }"

对于您上述的需求,这可以简化,因为您知道有参数传递到批处理文件中进行处理:

powershell -c "Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList '/foo'
  • %~dpnx0 - 自动批处理变量,这是当前脚本的完整路径,包括脚本名称。
  • %* - 自动批处理变量,这是传递到脚本中的所有参数。
  • ('%*' -split '\s'):这是一个 PowerShell 表达式,它将以空格为分隔符的 %* 变量拆分为连续的空格,并返回一个数组。为了简单起见,它会在双引号之间的空格上进行拆分,但如果需要,可以调整正则表达式来解决这个问题。

这篇答案值得一读,因为它介绍了其他自动批处理变量,你可能会在未来用到它们。


这可能是一个优化使用%~dpnx0或更好地使用%~f0的注释。但它并不能完全解决问题。顺便说一下,如果存在任何参数,则''%~dpnx0 %*'"会失败。 - jeb
啊,我看到我的错误了,我会修复它的。 - codewario
感谢对变量的详细解释!有一个小误解:我的用例需要通过提升的调用引入额外的参数 /foo,因此简化的解决方案将是 powershell -c "Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList /foo"(请注意您的帖子中缺少结束的 ")。但这可能只是我的专业用例,并且透明地传递所有参数是更常见的情况。不幸的是,您的广义建议不起作用。即使使用了 EnableDelayedExpansion,我仍然获得 1 作为退出代码而不是 0 - Twonky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接