Командлет PowerShell Start-Process
:
- имеет параметры
-RedirectStandardOut
и -RedirectStandardError
,
- но синтаксически их нельзя сочетать с
-Verb Runas
, аргументом, необходимым для запуска процесса с повышенными правами (с правами администратора).
Это ограничение также отражено в базовом API .NET, где установка свойства .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
, потому что это позволяет добавить перенаправление к команде: *>
перенаправляет все выходные потоки.
Сам @soleil предлагает использовать Tee-Object
в качестве альтернативы, чтобы вывод, создаваемый процессом с повышенными правами, не только захватывался, но и выводился на консоль (всегда новое окно) по мере его создания:
..., $arg, '|', 'Tee-Object', '-FilePath', "$PSScriptRoot\out.txt"
.
Предупреждение: хотя в этом простом случае это не имеет значения, важно знать, что аргументы обрабатываются по-разному в режимах -File
и -Command
; в двух словах, с -File
аргументы, следующие за именем сценария, обрабатываются как литералы, тогда как аргументы, следующие за -Command
, формируют команду, которая оценивается в соответствии с обычными правилами PowerShell в целевом сеансе, что имеет значение для побег, например; в частности, значения со встроенными пробелами должны быть заключены в кавычки как часть значения.
Компонент пути $PSScriptRoot\
в выходном файле захвата $PSScriptRoot\out.txt
гарантирует, что файл будет создан в той же папке, что и вызывающий скрипт (процессы с повышенными правами по умолчанию используют $env:SystemRoot\System32
в качестве рабочего каталога).
- Similarly, this means that script file
servicemssql.ps1
, if it is invoked without a path component, must be in one of the directories listed in $env:PATH
in order for the elevated PowerShell instance to find it; otherwise, a full path is also required, such as $PSScriptRoot\servicemssql.ps1
.
-Wait
гарантирует, что управление не вернется до тех пор, пока процесс с повышенными правами не завершится, после чего файл $PSScriptRoot\out.txt
может быть проверен.
Что касается следующего вопроса:
Чтобы пойти еще дальше, можем ли мы иметь способ сделать так, чтобы оболочка администратора работала незаметно, и читать файл, когда мы идем с Unix-эквивалентом tail -f
из непривилегированной оболочки?
Можно запустить сам процесс с повышенными правами незаметно, но учтите, что вы все равно получите запрос подтверждения UAC. (Если бы вы отключили UAC (не рекомендуется), вы могли бы использовать Start-Process -NoNewWindow
для запуска процесса в том же окне.)
Чтобы также отслеживать выходные данные по мере их создания, в стиле tail -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
}
}
# ...
person
mklement0
schedule
09.06.2018
-Verb Runas
) с помощьюStart-Process
- person Maximilian Burszley   schedule 08.06.2018ProcessInfo
вместе сProcess.Start
- person Maximilian Burszley   schedule 08.06.2018