Запуск .ps1 сценарію з PowerShell з параметрами та обліковими записами та отримання виводу за допомогою змінної


10

Привіт Стек Спільноти :)

У мене проста мета. Я хотів би запустити сценарій PowerShell з іншого сценарію Powershell, але є 3 умови:

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

Існує аналогічне питання Посилання . Але відповідь - використовувати файли як спосіб спілкування між 2-х сценаріями PS. Я просто хотів би уникнути конфліктів з доступом. @Update: Головний сценарій запустить кілька інших сценаріїв. тому рішення з файлами може бути складним, якщо виконання буде виконано від декількох користувачів одночасно.

Script1.ps1 - це сценарій, який повинен мати рядок як вихід. (Просто, щоб було зрозуміло, це вигаданий сценарій, у справжнього - 150 рядків, тому я просто хотів зробити приклад)

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

ExecuteScripts.ps1 повинен викликати ту, що має ці 3 умови, згадані вище

Я спробував кілька рішень. Цей для Examplte:

$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 CLI представляти вихід у форматі 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]екземпляра ( System.DateTime) Get-Dateбув десеріалізований як такий:

Running `Get-Date` as user jdoe yielded:

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

as data type:
System.DateTime

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

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. Сценарій, який виконується, - це підкидання рядка слова singe в кінці. Подивіться, як я це зробив, Start-processале ця функція не дає результату
Dmytro

Я думаю, що повноваження не дозволяє передавати такий параметр, як цей $ argument = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName. Ви, мабуть, повинні подумати про видалення знаку "-"
Енді Макрі

1
що бджіл сказав. Start-Process виконує сценарій з параметрами та обліковими записами, але він не зберігає цей вихід у змінну. Якщо я намагаюся отримати доступ до $outputзмінної, це NULL. Інша ідея, що виникла з @ mklement0, - це збереження виводу у файл. Але в моєму випадку це призведе до величезної кількості файлів в одному місці. Усі створені від різних користувачів з різними сценаріями
Дмитро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.