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

通过PowerShell调用具有提升权限的批处理文件并检索退出代码,powershell,batch-file,return-value,Powershell,Batch File,Return Value,我的Windows批处理应由没有管理员权限的用户启动。在某一步,它将以提升的权限调用自己。我了解到,使用PowerShell的runas功能(batch.bat)可以实现这一点⭢ 动力壳⭢ batch.bat)。这很有魅力 不幸的是,我无法从提升的批处理执行中接收退出代码。我总是收到1,尽管没有任何错误消息。我不知道退出代码在哪一次返回时丢失,第一次(批处理返回PowerShell)还是第二次(PowerShell返回批处理) 我相信,我已经尝试了所有类似问题的建议答案,但显然我无法继续。我需要

我的Windows批处理应由没有管理员权限的用户启动。在某一步,它将以提升的权限调用自己。我了解到,使用PowerShell的
runas
功能(batch.bat)可以实现这一点⭢ 动力壳⭢ 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%.
)

Nota bene(编辑以消除误解):虽然非提升调用(由用户)不需要任何参数,但提升调用引入了一个附加参数“/foo”。这对我来说更糟,因为我没有找到不丢失此参数的解决方案。但是,这似乎是一个非常不寻常的用例。

您没有将要执行的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表达式,它接受以空格分隔的
    %*
    变量,并在连续空格上拆分它,返回一个数组。为简单起见,这确实有一个缺点,即它将在双引号之间的空格上拆分,但如果需要,可以对正则表达式进行调整以说明这一点

对于将来可能使用的其他自动批处理变量,值得一读。

要解决参数问题,可以使用

powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
退出代码问题:
第一个问题是线路

echo powershell returned %errorlevel%.
这无法工作,因为它位于代码块内,
%errorlevel%
甚至在调用powershell之前都会展开,因此它始终为1-这是
openfiles/local…

但即使延迟扩展,我也总是得到0,可能是因为它是成功运行的
runas
的exitcode,它能够启动批处理

您可以使用变通方法并将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!.
)
@echo关闭
setlocal EnableDelayedExpansion
回波参数=%*
openfiles/local>nul 2>&1
如果%ERRORLEVEL%EQU 0(
回声升高,13号出口
暂停
echo 13>%temp%\\u exitcode.tmp
rem***在这里使用14表示它不存储在errorlevel中
14号出口
)否则(
回声未升高。正在尝试升高。
powershell启动进程-等待-动词运行方式“%0”-argumentlist'/additional arg%*'
设置/p\u exitcode=<%temp%\\u exitcode.tmp”
删除“%temp%\\u exitcode.tmp”
echo powershell已返回!\u exitcode!,lvl!errorlevel!。
)

这可能是一条注释,用于优化使用
%~dpnx0
或更好地使用
%~f0
。但这根本解决不了问题。顺便说一句,
'%~dpnx0%*“
失败,如果有任何参数存在。是的,我看到了我的错误,我会修正它。请欣赏对变量的严格解释!小误解:我的用例要求提升调用引入额外的参数
/foo
,因此简化的解决方案是
powershell-c”启动进程-Wait-Verb runas“%~dpnx0”-ArgumentList/foo”
(请注意文章中缺少的结束语
)。但这可能只是我的专用用例,更常见的情况是透明地传递所有参数。不幸的是,你的笼统建议不起作用。即使启用了
EnableDelayedExpansion
我也会将
1
作为退出代码,而不是
0
。抱歉。我删除了
EnableDelayedExpansion
,以便在“发现”它没有任何影响后将示例最小化。我注意到我错了。接下来,我从你的答案和你的帖子中了解到,我发现了bbb(批处理初学者bug),需要惊叹号扩展。然而,我不喜欢建议的解决方案,即为此创建临时文件,这对我来说似乎有点骇客,但很好。它至少起作用了,这太棒了@Twonky你是对的,这只是一个解决办法。我用powershell测试了它-PassThru但没有更好的结果将
\u exitcode.tmp
移动到
%temp%
并使用
设置
命令(如果存在)
。考虑到这个解决方案已经足够好了!谢谢,你太棒了!