通过启动进程以管理员身份从 powershell 脚本重定向 stdout、stderr
redirect stdout, stderr from powershell script as admin through start-process
在 powershell 脚本中,我是 运行 一个命令,它以管理员身份启动一个新的 powershell(如果我不是,如果需要,取决于 $arg
),然后运行脚本.
我正在尝试将 stdout 和 stderr 重定向到第一个终端。
不是为了让事情变得更简单,也有争论。
param([string]$arg="help")
if($arg -eq "start" -Or $arg -eq "stop")
{
if().groups -match "S-1-5-32-544"))
{
Start-Process powershell -Verb runas -ArgumentList " -file servicemssql.ps1 $arg"
exit
}
}
$Services = "MSSQLSERVER", "SQLSERVERAGENT", "MSSQLServerOLAPService", "SSASTELEMETRY", "SQLBrowser", `
"SQLTELEMETRY", "MSSQLLaunchpad", "SQLWriter", "MSSQLFDLauncher"
function startsql {
"starting SQL services"
Foreach ($s in $Services) {
"starting $s"
Start-Service -Name "$s"
}
}
function stopsql {
"stopping SQL services"
Foreach ($s in $Services) {
"stopping $s"
Stop-Service -Force -Name "$s"
}
}
function statussql {
"getting SQL services status"
Foreach ($s in $Services) {
Get-Service -Name "$s"
}
}
function help {
"usage: StartMssql [status|start|stop]"
}
Switch ($arg) {
"start" { startsql }
"stop" { stopsql }
"status" { statussql }
"help" { help }
"h" { help }
}
在 SO 上使用以下答案无效:
- Capturing standard out and error with Start-Process
- Powershell: Capturing standard out and error with Process object
如何在保留变量($arg
)扩展的同时处理双引号内的双引号?
PowerShell 的 Start-Process
cmdlet:
- 确实有
-RedirectStandardOut
和 -RedirectStandardError
参数,
- 但语法上它们不能与
-Verb Runas
组合,启动进程所需的参数提升(具有管理权限).
此约束也反映在基础 .NET API 中,其中将 System.Diagnostics.ProcessStartInfo
实例上的 .UseShellExecute
属性 设置为 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
用于调用脚本,因为这允许将重定向附加到命令:*>
重定向所有输出流。
@soleil 建议使用 Tee-Object
作为替代方案,这样提升过程产生的输出不仅会被捕获,还会打印到(总是新的 window' s) 正在制作的控制台:
..., $arg, '|', 'Tee-Object', '-FilePath', "`"$PSScriptRoot\out.txt`""
警告:虽然在这个简单的情况下没有什么区别,但重要的是要知道参数在 -File
和 -Command
模式之间的解析方式不同;简而言之,在 -File
中,脚本名称后面的参数被视为 文字 ,而 -Command
后面的参数构成一个根据正常计算的命令目标会话中的 PowerShell 规则,例如,这对转义有影响;值得注意的是,带有嵌入空格的值必须用引号括起来作为值的一部分。
输出捕获文件 $PSScriptRoot\out.txt
中的 $PSScriptRoot\
路径组件确保文件是在与调用脚本相同的文件夹中创建的(提升的进程默认为 $env:SystemRoot\System32
作为工作目录。)
- 同样,这意味着脚本文件
servicemssql.ps1
,如果在没有路径组件的情况下调用,则必须位于 $env:PATH
中列出的目录之一中,以便提升的 PowerShell 实例能够找到它;否则,还需要完整路径,例如 $PSScriptRoot\servicemssql.ps1
.
-Wait
确保控制不会 return 直到提升的进程退出,此时可以检查文件 $PSScriptRoot\out.txt
。
至于后续问题:
To go even further, could we have a way to have the admin shell running non visible, and read the file as we go with the Unix equivalent of tail -f
from the non -privileged shell ?
可以 运行 提升的进程本身不可见,但请注意,您仍然会收到 UAC 确认提示。 (如果你要关闭 UAC(不推荐),你可以在相同的 window 中使用 Start-Process -NoNewWindow
到 运行 进程。)
为了在生成输出时也监控输出,tail -f
-style,仅使用 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
}
}
# ...
在 powershell 脚本中,我是 运行 一个命令,它以管理员身份启动一个新的 powershell(如果我不是,如果需要,取决于 $arg
),然后运行脚本.
我正在尝试将 stdout 和 stderr 重定向到第一个终端。
不是为了让事情变得更简单,也有争论。
param([string]$arg="help")
if($arg -eq "start" -Or $arg -eq "stop")
{
if().groups -match "S-1-5-32-544"))
{
Start-Process powershell -Verb runas -ArgumentList " -file servicemssql.ps1 $arg"
exit
}
}
$Services = "MSSQLSERVER", "SQLSERVERAGENT", "MSSQLServerOLAPService", "SSASTELEMETRY", "SQLBrowser", `
"SQLTELEMETRY", "MSSQLLaunchpad", "SQLWriter", "MSSQLFDLauncher"
function startsql {
"starting SQL services"
Foreach ($s in $Services) {
"starting $s"
Start-Service -Name "$s"
}
}
function stopsql {
"stopping SQL services"
Foreach ($s in $Services) {
"stopping $s"
Stop-Service -Force -Name "$s"
}
}
function statussql {
"getting SQL services status"
Foreach ($s in $Services) {
Get-Service -Name "$s"
}
}
function help {
"usage: StartMssql [status|start|stop]"
}
Switch ($arg) {
"start" { startsql }
"stop" { stopsql }
"status" { statussql }
"help" { help }
"h" { help }
}
在 SO 上使用以下答案无效:
- Capturing standard out and error with Start-Process
- Powershell: Capturing standard out and error with Process object
如何在保留变量($arg
)扩展的同时处理双引号内的双引号?
PowerShell 的 Start-Process
cmdlet:
- 确实有
-RedirectStandardOut
和-RedirectStandardError
参数, - 但语法上它们不能与
-Verb Runas
组合,启动进程所需的参数提升(具有管理权限).
此约束也反映在基础 .NET API 中,其中将 System.Diagnostics.ProcessStartInfo
实例上的 .UseShellExecute
属性 设置为 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
用于调用脚本,因为这允许将重定向附加到命令:*>
重定向所有输出流。@soleil 建议使用
Tee-Object
作为替代方案,这样提升过程产生的输出不仅会被捕获,还会打印到(总是新的 window' s) 正在制作的控制台:
..., $arg, '|', 'Tee-Object', '-FilePath', "`"$PSScriptRoot\out.txt`""
警告:虽然在这个简单的情况下没有什么区别,但重要的是要知道参数在
-File
和-Command
模式之间的解析方式不同;简而言之,在-File
中,脚本名称后面的参数被视为 文字 ,而-Command
后面的参数构成一个根据正常计算的命令目标会话中的 PowerShell 规则,例如,这对转义有影响;值得注意的是,带有嵌入空格的值必须用引号括起来作为值的一部分。
输出捕获文件
$PSScriptRoot\out.txt
中的$PSScriptRoot\
路径组件确保文件是在与调用脚本相同的文件夹中创建的(提升的进程默认为$env:SystemRoot\System32
作为工作目录。)- 同样,这意味着脚本文件
servicemssql.ps1
,如果在没有路径组件的情况下调用,则必须位于$env:PATH
中列出的目录之一中,以便提升的 PowerShell 实例能够找到它;否则,还需要完整路径,例如$PSScriptRoot\servicemssql.ps1
.
- 同样,这意味着脚本文件
-Wait
确保控制不会 return 直到提升的进程退出,此时可以检查文件$PSScriptRoot\out.txt
。
至于后续问题:
To go even further, could we have a way to have the admin shell running non visible, and read the file as we go with the Unix equivalent of
tail -f
from the non -privileged shell ?
可以 运行 提升的进程本身不可见,但请注意,您仍然会收到 UAC 确认提示。 (如果你要关闭 UAC(不推荐),你可以在相同的 window 中使用 Start-Process -NoNewWindow
到 运行 进程。)
为了在生成输出时也监控输出,tail -f
-style,仅使用 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
}
}
# ...