动力壳
Start-Process
cmdlet:
-
确实有
-RedirectStandardOut
和
-RedirectStandardError
参数,
-
但是
句法上
它们不能与
-Verb Runas
,启动进程所需的参数。
高架
(具有管理权限)。
此约束也反映在底层的.NET API中,其中设置
.UseShellExecute
上的属性
System.Diagnostics.ProcessStartInfo
实例到
true
-能够使用的先决条件
.Verb = "RunAs"
为了运行提升-意味着您不能使用
.RedirectStandardOutput
和
.RedirectStandardError
性质。
总的来说,这表明你不能
直接地
从非提升的进程捕获提升的进程的输出流。
一
纯powershell解决方案
不是小事:
param([string] $arg='help')
if ($arg -in 'start', 'stop') {
if (-not (([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators'))) {
# Invoke the script via -Command rather than -File, so that
# a redirection can be specified.
$passThruArgs = '-command', '&', 'servicemssql.ps1', $arg, '*>', "$PSScriptRoot\out.txt"
Start-Process powershell -Wait -Verb RunAs -ArgumentList $passThruArgs
# Retrieve the captured output streams here:
Get-Content "$PSScriptRoot\out.txt"
exit
}
}
# ...
-
而不是
-File
,
-Command
用于调用脚本,因为这允许将重定向附加到命令:
*>
重定向所有输出流。
-
@索莱尔自己建议使用
Tee-Object
另一种方法是,在生成提升过程的输出时,不仅捕获该输出,还将其打印到(始终是新窗口的)控制台:
..., $arg, '|', 'Tee-Object', '-FilePath', "$PSScriptRoot\out.txt"
.
-
注意:虽然在这个简单的例子中没有什么区别,但是重要的是要知道参数在
-文件
和
-命令
模式;简而言之
-文件
,脚本名后面的参数被视为
字面量
,而下面的论点
-命令
形成一个命令,该命令根据目标会话中的常规powershell规则进行求值,例如,这对转义有影响;值得注意的是,带有嵌入空格的值必须用引号括起来作为值的一部分。
-
这个
$PSScriptRoot\
输出捕获文件中的路径组件
$PSScriptRoot\out.txt
确保在调用脚本所在的文件夹中创建文件(提升的进程默认为
$env:SystemRoot\System32
作为工作主管。)
-
类似地,这意味着脚本文件
servicemssql.ps1
,如果在没有路径组件的情况下调用它,则必须位于
$env:PATH
为了让提升的powershell实例找到它;否则,还需要完整的路径,例如
$PSScriptRoot\servicemssql.ps1
.
-
-Wait
确保在提升的进程退出之前控件不会返回,此时文件
$psscriptroot\out.txt文件
可以检查。
关于后续问题:
更进一步说,我们是否可以有一种方法使管理shell运行不可见,并在使用unix时读取文件
tail -f
从没有特权的外壳?
可以在不可见的情况下运行提升的进程本身,但请注意,您仍然会得到uac确认提示。(如果要关闭UAC(不推荐),可以使用
Start-Process -NoNewWindow
在同一窗口中运行进程。)
还
在生产过程中监控输出,
尾F
风格
,仅限powershell的解决方案既不平凡,也不是最有效的;换句话说:
param([string]$arg='help')
if ($arg -in 'start', 'stop') {
if (-not (([System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators'))) {
# Delete any old capture file.
$captureFile = "$PSScriptRoot\out.txt"
Remove-Item -ErrorAction Ignore $captureFile
# Start the elevated process *hidden and asynchronously*, passing
# a [System.Diagnostics.Process] instance representing the new process out, which can be used
# to monitor the process
$passThruArgs = '-noprofile', '-command', '&', "servicemssql.ps1", $arg, '*>', $captureFile
$ps = Start-Process powershell -WindowStyle Hidden -PassThru -Verb RunAs -ArgumentList $passThruArgs
# Wait for the capture file to appear, so we can start
# "tailing" it.
While (-not $ps.HasExited -and -not (Test-Path -LiteralPath $captureFile)) {
Start-Sleep -Milliseconds 100
}
# Start an aux. background that removes the capture file when the elevated
# process exits. This will make Get-Content -Wait below stop waiting.
$jb = Start-Job {
# Wait for the process to exit.
# Note: $using:ps cannot be used directly, because, due to
# serialization/deserialization, it is not a live object.
$ps = (Get-Process -Id $using:ps.Id)
while (-not $ps.HasExited) { Start-Sleep -Milliseconds 100 }
# Get-Content -Wait only checks once every second, so we must make
# sure that it has seen the latest content before we delete the file.
Start-Sleep -Milliseconds 1100
# Delete the file, which will make Get-Content -Wait exit (with an error).
Remove-Item -LiteralPath $using:captureFile
}
# Output the content of $captureFile and wait for new content to appear
# (-Wait), similar to tail -f.
# `-OutVariable capturedLines` collects all output in
# variable $capturedLines for later inspection.
Get-Content -ErrorAction SilentlyContinue -Wait -OutVariable capturedLines -LiteralPath $captureFile
Remove-Job -Force $jb # Remove the aux. job
Write-Verbose -Verbose "$($capturedLines.Count) line(s) captured."
exit
}
}
# ...