Powershell 命令不再起作用 - 仅以管理员身份运行
我不太擅长 PS,所以我决定寻求一些建议。
我有一个 VBA 脚本,它在 PS 命令中使用
Get-DhcpServerv4Lease
来查询 DHCP 服务器(作为域管理员用户)给定范围并将返回的数据排列到 Excel 中。
strCommand = "%SystemRoot%\system32\WindowsPowerShell\v1.0\Powershell.exe start-job -credential <domain>\" & TheAdminUser & " -ScriptBlock{Get-DhcpServerv4Lease -ComputerName '<DHCP server>' -ScopeId " & TheScope & "} | wait-job | receive-job"
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(strCommand)
strOutput = WshShellExec.StdOut.ReadAll
该脚本以前运行正常,现在由于某些未知原因,它不再运行。
尝试在 PS 中手动运行该命令,发现它现在只有在我以管理员身份运行 PS 时才有效(即使以本地管理员身份运行也能工作),否则它会返回以下错误:
[localhost] An error occurred while starting the background process. Error reported: The directory name is invalid.
+ CategoryInfo : OpenError: (localhost:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : -2147467259,PSSessionStateBroken
有什么建议,可能是什么问题,或者我可以从哪里开始寻找解决方案?
更新:
与此同时,我找到了一种不同的解决方法,可以修复我使用的原始代码。
添加
[environment]::CurrentDirectory='C:\Windows\System32\WindowsPowerShell\v1.0';
使其再次运行且不会出现错误。
strCommand = "%SystemRoot%\system32\WindowsPowerShell\v1.0\Powershell.exe "[environment]::CurrentDirectory='C:\Windows\System32\WindowsPowerShell\v1.0';start-job -credential <domain>\" & TheAdminUser & " -ScriptBlock{Get-DhcpServerv4Lease -ComputerName '<DHCP server>' -ScopeId " & TheScope & "} | wait-job | receive-job"
这些答案非常有用,也许对其他人来说可以作为替代解决方法,并且让我更接近更好地理解 Powershell。
Any advice what might be the problem
该问题是
Start-Job
cmdlet
中的一个错误,该错误会影响 Windows PowerShell(v5.1,最新和最终版本)和 PowerShell (Core) v6+(尽管症状不同,但截至 PowerShell Core 7.2.0-preview.8 仍然如此 - 请参阅
GitHub 问题 #7172
)。
在
Windows PowerShell
中,后台进程使用
调用
用户主目录树中的(固定)工作目录(
Documents
文件夹),而将凭据传递给
-Credential
的用户则无权访问该文件夹
,从而导致您看到的错误 - 并且无法指定
不同的
工作目录。
-
Start-Job -Credential
确实 起作用, 如果您的会话得到提升 ,即以管理员权限运行,在这种情况下将切换到 目标 用户的Documents
文件夹。但是,鉴于标准runas.exe
实用程序甚至可以从 非提升 会话中以不同用户身份调用命令,因此不需要此要求。 -
此外 - 正如您自己发现的那样 - 有一个 解决方法 :
-
如果您明确 将 进程级 工作目录(与 PowerShell 不同)设置为目标用户被允许访问的目录 ,则
Start-Job -Credential
可以工作;例如,您可以使用C:\
或$env:SYSTEMROOT\Windows32
(后者是runas.exe
使用的);一个简单的例子(用感兴趣的用户名替换otheruser
):[Environment]::CurrentDirectory = 'C:\' # 设置进程级工作目录。 Start-Job -Credential (Get-Credential otheruser) { whoami } | Receive-Job -Wait -AutoRemoveJob
-
PowerShell (Core)
现在使后台进程继承调用者的作业目录(调用者可以将其设置为目标用户可以访问的目录),并且还具有
-WorkingDirectory
参数,但从 PowerShell Core 7.2.0-preview 开始,这两种方法都无法解决
-Credential
的问题 - 即使您
使用提升
运行(并且上述解决方法也无济于事)。
根据您问题的更新,似乎解决方法解决了您的问题,这意味着您 不 需要以域用户身份运行的操作 提升 ;对于需要提升权限的用例,以下内容可能仍然有用。
如果您需要运行使用具有提升权限(具有管理员权限)的不同用户身份的操作:
启动一个
提升
(以管理员身份运行)进程是
Start-Job -Credential
根本无法
提供的。
启动提升进程的唯一(PowerShell 原生)方法是通过
Start-Process
-Verb RunAs
。
-
注意:使用
Start-Process
意味着启动进程的 输出 不能被调用者直接捕获,而是需要通过--RedirectStandardOutput
和-RedirectStandardError
参数将输出发送到 文件 ,调用者(假设进程终止)等待 - 稍后可以读取。
因此,请尝试以下操作:
strCommand = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -c Start-Process -Wait -Verb RunAs powershell.exe \""-c Get-DhcpServerv4Lease -ComputerName '<DHCP server>' -ScopeId " & TheScope & "\"""
注意:
-
需要再次 调用
powershell.exe
,以提升(-Verb RunAs
)方式启动,同步(-Wait
),然后在前台执行Get-DhcpServerv4Lease
调用。-
由于
Start-Process -Verb RunAs
总是在 新 窗口中启动进程,因此您可能也希望通过将-WindowStyle Hidden
添加到Start-Process
调用来隐藏该窗口。相反,如果您 确实 想要看到该窗口,则可能需要使用 VBA 功能隐藏启动提升窗口的 中间 窗口。 -
注意:我已将
-c
(-Command
) 添加到powershell.exe
调用中,以提高概念清晰度;虽然此参数在powershell.exe
(Windows PowerShell) 中是 隐含的 ,但在 PowerShell (Core) 等效项pwsh.exe
中,默认值现在为-f
(-File
)。 -
还请注意,需要对嵌入的
"
字符进行\
转义。(对于 VBA,转义为""
),以便 PowerShell CLI 在解析命令行参数后将它们作为命令的一部分保留。
-
-
与您最初的尝试一样,这将 提示 输入密码,如果调用用户 原则上 没有管理权限,则还必须输入管理员的 用户名 。请注意,此提示无法阻止(除非您关闭 UAC ,这是不明智的)。
-
无法传递用于提升权限的管理员用户名
,因为
-Verb RunAs
与-Credential
互斥。-Verb RunAs
的逻辑是,如果当前用户是管理员用户(原则上), 它 总是 用于提升权限的会话,并且只会向您显示“是/否”确认对话框。因此,如果您需要 不同的 管理员用户,例如 域 管理员,则此方法无效 - 请参阅下文。 (仅当调用用户 不是 管理员用户时,UAC 提示才会明确要求输入用户名和密码)。
-
无法传递用于提升权限的管理员用户名
,因为
如果您需要使用 给定 管理员用户帐户 运行提升的会话:
不幸的是,这需要更深层次的嵌套命令,并且存在其他陷阱:
-
本质上,您需要首先调用
Start-Process -Credential
来与目标用户创建(必要的) 非提升 会话,然后允许您调用Start-Process -Verb RunAs
为该用户创建 提升 会话。- 警告 :这 需要您回答 两个 提示 :首先,您需要输入密码以启动管理员用户的非提升会话,然后您需要回答是/否 UAC 提示以确认启动该提升会话的意图用户。
-
加入了
Set-Location C:\
命令,以确保工作目录对目标用户有效(在初始非提升会话中)。 -
使用
Start-Process -Wait
等待以不同用户 (-Credential
) 启动的进程终止,在从非提升会话调用时,由于缺乏权限而莫名其妙地失败;解决方法是使用(Start-Process -PassThru ...).WaitForExit()
。 -
为了简化引用,仅使用
'...'
引用(在嵌套调用中转义为''...''
) - 因此,命令本身不能包含'
字符。
这会导致以下怪异现象。
' Assumes that the following variables are defined:
' TheAdminUser, TheComputerName, TheScope
strCommand = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -c Set-Location C:\; (Start-Process -WindowStyle Hidden -PassThru -Credential " & TheAdminUser & " powershell.exe ' -c Start-Process -Wait -Verb RunAs powershell.exe '' -c Get-DhcpServerv4Lease -ComputerName " & TheComputerName & " -ScopeId " & TheScope & " '' ').WaitForExit()"
注意:为了进行故障排除,请在
-c
参数前面加上
-noexit
以保持 PowerShell 会话打开。
替代方案,事先设置 :
正如 Joel Coehoorn 指出,允许非管理员用户(仅具有管理员权限)执行预配置操作的一种方法是设置一个使用管理员凭据运行并执行所需操作的 计划任务 ,然后非管理员用户可以 按需 调用该任务。
这将完全消除对密码的需求,但要确保该操作对于非管理员用户来说确实是安全的。
注意 :在某些情况下,由非管理员用户/非提升进程运行计划任务可能会 失败 - 尽管根据 Joel 的说法,它在当前情况下 确实 有效;正如他在参考 此 Server Fault 帖子 时指出的那样:
I think part of the problem was trying to run as SYSTEM rather than a specific privileged user in elevated mode. It also talks about contexts like SCCM and startup, where certain registry keys are not available, and the powershell code to invoke the task may also have changed.
您应该能够将
Start-Process -RunAs
用于
powershell.exe
命令,它将提升权限。请注意,如果 vBA 进程尚未提升权限,则此
将
触发 UAC。
关键在于,如果您尝试从
不同的
进程进行自我提升,
Start-Process
是一个 PowerShell cmdlet,因此您需要基本上运行 PowerShell 来运行另一个提升权限的 PowerShell 会话。该命令将如下所示:
powershell.exe -Command "Start-Process -Wait -Verb RunAs powershell.exe '-Command ""YOUR ELEVATED CODE HERE""'"
您可以在命令提示符中使用以下命令进行测试,该命令将输出“hello”,等待按键,然后退出:
powershell.exe -Command "Start-Process -Wait -Verb RunAs powershell.exe '-Command ""echo hello; cmd /c pause""'"
请注意,这是您从命令行调用命令的方式,无论是 PowerShell 还是 CMD。如果从其他语言调用,则可能需要调整转义序列。
当从其他地方调用
powershell.exe
或
pwsh
并希望在命令完成后退出会话时,您还应该使用
-Command
参数,或者对于相同的但它是一个脚本的
-File
参数。
调用
Start-Process
时,您还需要使用
-Wait
参数,否则它不会阻塞。这与一般的可执行调用模式相反,非 GUI 程序通常不需要
-Wait
参数来阻塞直到进程退出。