Запуск сценария .ps1 из PowerShell с параметрами и учетными данными и получение вывода с использованием переменной

10

Hello Stack Community :)

У меня есть простая цель. Я хотел бы запустить сценарий PowerShell из другого сценария Powershell, но есть 3 условия:

  1. Я должен передать учетные данные (выполнение подключается к базе данных, которая имеет конкретного пользователя)
  2. Это должно принять некоторые параметры
  3. Я хотел бы передать вывод в переменную

Есть похожий вопрос Ссылка . Но ответ заключается в том, чтобы использовать файлы как способ связи между двумя сценариями PS. Я просто хотел бы избежать конфликтов доступа. @ Обновление: основной сценарий собирается запустить несколько других сценариев. поэтому решение с файлами может быть сложным, если выполнение будет выполняться от нескольких пользователей одновременно.

Script1.ps1 - это скрипт, который должен иметь строку в качестве вывода. (Просто чтобы прояснить, это вымышленный сценарий, реальный имеет 150 строк, поэтому я просто хотел привести пример)

param(  
[String]$DeviceName
)
#Some code that needs special credentials
$a = "Device is: " + $DeviceName
$a

ExecuteScripts.ps1 должен вызвать тот с этими 3 условиями, упомянутыми выше

Я пробовал несколько решений. Это для примера:

$arguments = "C:\..\script1.ps1" + " -ClientName" + $DeviceName
$output = Start-Process powershell -ArgumentList $arguments -Credential $credentials
$output 

Я не получаю вывод от этого, и я не могу просто вызвать скрипт с

&C:\..\script1.ps1 -ClientName PCPC

Потому что я не могу передать -Credentialпараметр к нему ..

Заранее спасибо!

Дмитро
источник
Если речь идет только о конфликтах доступа: создание уникальных имен файлов для каждого вызова решит вашу проблему, верно?
mklement0
1
@ mklement0, если это единственный способ, я бы использовал это решение. Просто генерируя случайные имена файлов, проверяя, существует ли такой файл ... Я буду выполнять от 6 до 10 сценариев из своего Java-кода, и мне потребуется 6-10 файлов каждый раз, когда я использую или кто-то еще использует мое приложение. Так что о производительности тоже
Дмитрий

Ответы:

2

Замечания:

  • Следующее решение работает с любой внешней программой и сохраняет выходные данные в виде текста .

  • Чтобы вызвать другой экземпляр PowerShell и записать его выходные данные как расширенные объекты (с ограничениями), см. Вариант решения в нижнем разделе или рассмотрите полезный ответ Матиаса Р. Джессена , в котором используется PowerShell SDK .

Вот подтверждение концепции, основанное на прямом использовании типов System.Diagnostics.Processи System.Diagnostics.ProcessStartInfo.NET для захвата выходных данных процесса в памяти (как указано в вашем вопросе, Start-Processне вариант, потому что он поддерживает только захват выходных данных в файлах , как показано в этом ответе ) :

Замечания:

  • Поскольку он работает от имени другого пользователя, он поддерживается только в Windows (начиная с .NET Core 3.1), но в обеих версиях PowerShell.

  • Из - за необходимости запуска в качестве другого пользователя и необходимости вывода захвата, .WindowStyleне может быть использована для запуска команды скрытый (потому что использование .WindowStyleтребует , .UseShellExecuteчтобы быть $true, что несовместимо с этими требованиями); однако, поскольку весь вывод фиксируется , установка .CreateNoNewWindowна $trueрезультат приводит к скрытому выполнению.

# Get the target user's name and password.
$cred = Get-Credential

# Create a ProcessStartInfo instance
# with the relevant properties.
$psi = [System.Diagnostics.ProcessStartInfo] @{
  # For demo purposes, use a simple `cmd.exe` command that echoes the username. 
  # See the bottom section for a call to `powershell.exe`.
  FileName = 'cmd.exe'
  Arguments = '/c echo %USERNAME%'
  # Set this to a directory that the target user
  # is permitted to access.
  WorkingDirectory = 'C:\'                                                                   #'
  # Ask that output be captured in the
  # .StandardOutput / .StandardError properties of
  # the Process object created later.
  UseShellExecute = $false # must be $false
  RedirectStandardOutput = $true
  RedirectStandardError = $true
  # Uncomment this line if you want the process to run effectively hidden.
  #   CreateNoNewWindow = $true
  # Specify the user identity.
  # Note: If you specify a UPN in .UserName
  # (user@doamin.com), set .Domain to $null
  Domain = $env:USERDOMAIN
  UserName = $cred.UserName
  Password = $cred.Password
}

# Create (launch) the process...
$ps = [System.Diagnostics.Process]::Start($psi)

# Read the captured standard output.
# By reading to the *end*, this implicitly waits for (near) termination
# of the process.
# Do NOT use $ps.WaitForExit() first, as that can result in a deadlock.
$stdout = $ps.StandardOutput.ReadToEnd()

# Uncomment the following lines to report the process' exit code.
#   $ps.WaitForExit()
#   "Process exit code: $($ps.ExitCode)"

"Running ``cmd /c echo %USERNAME%`` as user $($cred.UserName) yielded:"
$stdout

Выше приведено что-то вроде следующего, показывающее, что процесс успешно запущен с заданным идентификатором пользователя:

Running `cmd /c echo %USERNAME%` as user jdoe yielded:
jdoe

Поскольку вы вызываете другой экземпляр PowerShell , вы можете воспользоваться преимуществами возможности интерфейса командной строки PowerShell представлять выходные данные в формате CLIXML, что позволяет десериализовать выходные данные в расширенные объекты , хотя и с ограниченной точностью типов , как объясняется в этом связанном ответе. ,

# Get the target user's name and password.
$cred = Get-Credential

# Create a ProcessStartInfo instance
# with the relevant properties.
$psi = [System.Diagnostics.ProcessStartInfo] @{
  # Invoke the PowerShell CLI with a simple sample command
  # that calls `Get-Date` to output the current date as a [datetime] instance.
  FileName = 'powershell.exe'
  # `-of xml` asks that the output be returned as CLIXML,
  # a serialization format that allows deserialization into
  # rich objects.
  Arguments = '-of xml -noprofile -c Get-Date'
  # Set this to a directory that the target user
  # is permitted to access.
  WorkingDirectory = 'C:\'                                                                   #'
  # Ask that output be captured in the
  # .StandardOutput / .StandardError properties of
  # the Process object created later.
  UseShellExecute = $false # must be $false
  RedirectStandardOutput = $true
  RedirectStandardError = $true
  # Uncomment this line if you want the process to run effectively hidden.
  #   CreateNoNewWindow = $true
  # Specify the user identity.
  # Note: If you specify a UPN in .UserName
  # (user@doamin.com), set .Domain to $null
  Domain = $env:USERDOMAIN
  UserName = $cred.UserName
  Password = $cred.Password
}

# Create (launch) the process...
$ps = [System.Diagnostics.Process]::Start($psi)

# Read the captured standard output, in CLIXML format,
# stripping the `#` comment line at the top (`#< CLIXML`)
# which the deserializer doesn't know how to handle.
$stdoutCliXml = $ps.StandardOutput.ReadToEnd() -replace '^#.*\r?\n'

# Uncomment the following lines to report the process' exit code.
#   $ps.WaitForExit()
#   "Process exit code: $($ps.ExitCode)"

# Use PowerShell's deserialization API to 
# "rehydrate" the objects.
$stdoutObjects = [Management.Automation.PSSerializer]::Deserialize($stdoutCliXml)

"Running ``Get-Date`` as user $($cred.UserName) yielded:"
$stdoutObjects
"`nas data type:"
$stdoutObjects.GetType().FullName

Выше приведено что-то вроде следующего, показывающее, что выход [datetime]instance ( System.DateTime) Get-Dateбыл десериализован так:

Running `Get-Date` as user jdoe yielded:

Friday, March 27, 2020 6:26:49 PM

as data type:
System.DateTime
mklement0
источник
5

Start-Processбудет моим последним выбором для вызова PowerShell из PowerShell - особенно потому, что все операции ввода-вывода становятся строками, а не (десериализованными) объектами.

Две альтернативы:

1. Если пользователь является локальным администратором и настроен PSRemoting

Если возможен удаленный сеанс на локальном компьютере (к сожалению, доступ ограничен локальными администраторами), я бы определенно выбрал Invoke-Command:

$strings = Invoke-Command -FilePath C:\...\script1.ps1 -ComputerName localhost -Credential $credential

$strings будет содержать результаты.


2. Если пользователь не является администратором в целевой системе

Вы можете написать свой собственный «только для локальных Invoke-Command», раскручивая пространство вне процесса:

  1. Создание PowerShellProcessInstanceпод другим логином
  2. Создание пространства выполнения в указанном процессе
  3. Выполните ваш код в указанном внепроцессном пространстве выполнения

Я собрал такую ​​функцию ниже, см. Встроенные комментарии для ознакомления:

function Invoke-RunAs
{
    [CmdletBinding()]
    param(
        [Alias('PSPath')]
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        ${FilePath},

        [Parameter(Mandatory = $true)]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential},

        [Alias('Args')]
        [Parameter(ValueFromRemainingArguments = $true)]
        [System.Object[]]
        ${ArgumentList},

        [Parameter(Position = 1)]
        [System.Collections.IDictionary]
        $NamedArguments
    )

    begin
    {
        # First we set up a separate managed powershell process
        Write-Verbose "Creating PowerShellProcessInstance and runspace"
        $ProcessInstance = [System.Management.Automation.Runspaces.PowerShellProcessInstance]::new($PSVersionTable.PSVersion, $Credential, $null, $false)

        # And then we create a new runspace in said process
        $Runspace = [runspacefactory]::CreateOutOfProcessRunspace($null, $ProcessInstance)
        $Runspace.Open()
        Write-Verbose "Runspace state is $($Runspace.RunspaceStateInfo)"
    }

    process
    {
        foreach($path in $FilePath){
            Write-Verbose "In process block, Path:'$path'"
            try{
                # Add script file to the code we'll be running
                $powershell = [powershell]::Create([initialsessionstate]::CreateDefault2()).AddCommand((Resolve-Path $path).ProviderPath, $true)

                # Add named param args, if any
                if($PSBoundParameters.ContainsKey('NamedArguments')){
                    Write-Verbose "Adding named arguments to script"
                    $powershell = $powershell.AddParameters($NamedArguments)
                }

                # Add argument list values if present
                if($PSBoundParameters.ContainsKey('ArgumentList')){
                    Write-Verbose "Adding unnamed arguments to script"
                    foreach($arg in $ArgumentList){
                        $powershell = $powershell.AddArgument($arg)
                    }
                }

                # Attach to out-of-process runspace
                $powershell.Runspace = $Runspace

                # Invoke, let output bubble up to caller
                $powershell.Invoke()

                if($powershell.HadErrors){
                    foreach($e in $powershell.Streams.Error){
                        Write-Error $e
                    }
                }
            }
            finally{
                # clean up
                if($powershell -is [IDisposable]){
                    $powershell.Dispose()
                }
            }
        }
    }

    end
    {
        foreach($target in $ProcessInstance,$Runspace){
            # clean up
            if($target -is [IDisposable]){
                $target.Dispose()
            }
        }
    }
}

Затем используйте так:

$output = Invoke-RunAs -FilePath C:\path\to\script1.ps1 -Credential $targetUser -NamedArguments @{ClientDevice = "ClientName"}
Матиас Р. Ессен
источник
0

rcv.ps1

param(
    $username,
    $password
)

"The user is:  $username"
"My super secret password is:  $password"

выполнение из другого скрипта:

.\rcv.ps1 'user' 'supersecretpassword'

вывод:

The user is:  user
My super secret password is:  supersecretpassword
thepip3r
источник
1
Я должен передать верительные грамоты на это представление ...
Дмитрий
обновил соответствующие порции.
thepip3r
Чтобы уточнить: цель состоит не только в передаче учетных данных, но и в качестве пользователя, идентифицированного учетными данными.
mklement0
1
@ mklement0, спасибо за разъяснения, потому что мне не совсем понятны разные итерации задаваемого вопроса.
thepip3r
0

Что вы можете сделать следующим образом, чтобы передать параметр в скрипт ps1.

Первый скрипт может быть origin.ps1, где мы пишем:

& C:\scripts\dest.ps1 Pa$$w0rd parameter_a parameter_n

Сценарий назначения dest.ps1 может иметь следующий код для захвата переменных

$var0 = $args[0]
$var1 = $args[1]
$var2 = $args[2]
Write-Host "my args",$var0,",",$var1,",",$var2

И результат будет

my args Pa$$w0rd, parameter_a, parameter_n
Энди МакРей
источник
1
Основная цель - объединить все условия в 1 исполнение. Я должен передать параметры и полномочия!
Дмитрий
Что вы имеете в виду под «объединить все условия в 1 исполнение». Я не думаю, что вы можете добавить параметр с символом "-", как вы сделали .. Я думаю, что вам нужно переформатировать строки в сценарии назначения
Энди МакРей
Я должен выполнить некоторый файл PS1 с параметрами и передать -Credential $credentialsпараметр для этого выполнения и получить вывод из него в переменную. PS1. Сценарий, который я выполняю, бросает отдельную строку слова в конце. Просто посмотрите, как я это сделал, Start-processно эта функция не генерирует вывод
Дмитрий
Я думаю, что powershell не позволяет вам передавать параметр, подобный этому, $ arguments = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName. Вы, вероятно, должны подумать об удалении "-"
Энди МакРей
1
это сказано. Start-Process выполняет сценарий с параметрами и учетными данными, но не сохраняет эти выходные данные в переменную. Если я пытаюсь получить доступ к $outputпеременной, это NULL. Другая идея, пришедшая от @ mklement0, заключается в сохранении вывода в файл. Но в моем случае это приведет к огромному количеству файлов в одном месте. Все создано от разных пользователей с разными скриптами
Дмитрий