Командлет PowerShell Start-Process
:
- имеет параметры
-RedirectStandardOut
и -RedirectStandardError
, - , но синтаксически их нельзя объединить с
-Verb Runas
аргумент, необходимый для запуска процесса повышенный (с правами администратора).
Это ограничение также отражено в базовом .NET API, где задается свойство .UseShellExecute
для экземпляров System.Diagnostics.ProcessStartInfo
до true
- предварительное условие для возможности использования .Verb = "RunAs"
для запуска с повышенными правами - означает, что вы не можете использовать свойства .RedirectStandardOutput
и .RedirectStandardError
.
В целом, это говорит о том, что вы не можете напрямую захватывать выходные потоки процесса с повышенными правами из процесса без прав доступа.
A Чистое решение 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
в качестве альтернативы, чтобы вывод, произведенный повышенным процессом, не только захватывался, но и печатался вконсоль (неизменно нового окна) в том виде, в каком она создается:
..., $arg, '|', 'Tee-Object', '-FilePath', "$PSScriptRoot\out.txt"
.
Предупреждение: хотя в этом простом случае это не имеет значения, важнознать, что аргументы по-разному анализируются между режимами -File
и -Command
;в двух словах, с -File
аргументы, следующие за именем скрипта, обрабатываются как литералы , тогда как аргументы после -Command
образуют команду, которая оценивается в соответствии с обычными правилами PowerShell в целевом сеансе, которыйнапример, имеет значение для побега;в частности, значения со встроенными пробелами должны быть заключены в кавычки как часть значения.
Компонент пути $PSScriptRoot\
в файле захвата вывода $PSScriptRoot\out.txt
обеспечиваетчто файл создается в той же папке, что и вызывающий скрипт (повышенные процессы по умолчанию $env:SystemRoot\System32
в качестве рабочего каталога.)
- Аналогично, это означает, что файл скрипта
servicemssql.ps1
, если онвызывается без компонента пути, должен находиться в одном из каталогов, перечисленных в $env:PATH
, чтобы экземпляр PowerShell с повышенными правами мог его найти;в противном случае также требуется полный путь, такой как $PSScriptRoot\servicemssql.ps1
.
-Wait
гарантирует, что управление не вернется, пока не завершится процесс с повышенными правами, и в этот моментфайл $PSScriptRoot\out.txt
можно просмотреть.
Что касается следующего вопроса:
Если пойти еще дальше, могли бы мы найти способ?чтобы оболочка администратора работала невидимой и читала файл по ходу использования Unix, эквивалентного tail -f
из непривилегированной оболочки?
Можно самому запускать процесс с повышенными правами незаметно, но учтите, что вы все равно получите запрос подтверждения UAC.(Если бы вы отключили UAC (не рекомендуется), вы могли бы использовать 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
}
}
# ...