Mulai .ps1 Script dari PowerShell dengan Parameter dan Kredensial dan dapatkan output menggunakan variabel

10

Hello Stack Community :)

Saya punya tujuan sederhana. Saya ingin memulai beberapa Skrip PowerShell dari Skrip Powershell lain, tetapi ada 3 ketentuan:

  1. Saya harus lulus kredensial (eksekusi terhubung ke database yang memiliki pengguna tertentu)
  2. Itu harus mengambil beberapa Parameter
  3. Saya ingin melewatkan output ke dalam variabel

Ada sejenis pertanyaan link . Tetapi jawabannya adalah menggunakan file sebagai cara untuk berkomunikasi antara 2 PS Script. Saya hanya ingin menghindari konflik akses. @ Pembaruan: Skrip Utama akan memulai beberapa skrip lainnya. jadi solusi dengan file bisa rumit, jika eksekusi akan dilakukan dari beberapa pengguna secara bersamaan.

Script1.ps1 adalah skrip yang harus memiliki string sebagai output. (Untuk lebih jelasnya, ini adalah naskah fiktif, yang asli memiliki 150 Baris, jadi saya hanya ingin membuat contoh)

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

ExecuteScripts.ps1 harus memohon yang satu dengan 3 kondisi yang disebutkan di atas

Saya mencoba beberapa solusi. Ini untuk contoh:

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

Saya tidak mendapatkan hasil dari itu dan saya tidak bisa begitu saja memanggil skrip

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

Karena saya tidak bisa mengirimkan -Credentialparameter ke sana ..

Terima kasih sebelumnya!

Dmytro
sumber
Jika ini hanya tentang konflik akses: membuat nama file unik untuk setiap doa akan menyelesaikan masalah Anda, bukan?
mklement0
1
@ mklement0 jika itu satu-satunya cara, saya akan menumpuk dengan solusi itu. Hanya membuat nama file acak, memeriksa apakah file tersebut ada ... Saya akan mengeksekusi 6 hingga 10 skrip dari Java Code saya dan akan membutuhkan 6 hingga 10 file setiap kali saya menggunakan atau orang lain menggunakan aplikasi saya. Jadi ini tentang kinerja juga
Dmytro

Jawaban:

2

catatan:

  • Solusi berikut ini berfungsi dengan program eksternal apa pun , dan menangkap output sebagai teks .

  • Untuk memanggil instance PowerShell lain dan menangkap outputnya sebagai objek kaya (dengan keterbatasan), lihat solusi varian di bagian bawah atau pertimbangkan jawaban Mathias R. Jessen yang bermanfaat , yang menggunakan PowerShell SDK .

Berikut adalah bukti konsep yang didasarkan pada penggunaan langsung tipe System.Diagnostics.Processdan System.Diagnostics.ProcessStartInfo.NET untuk menangkap output proses dalam memori (sebagaimana dinyatakan dalam pertanyaan Anda, Start-Processbukan opsi, karena hanya mendukung menangkap keluaran dalam file , seperti yang ditunjukkan dalam jawaban ini ) :

catatan:

  • Karena berjalan sebagai pengguna yang berbeda, ini hanya didukung pada Windows (pada .NET Core 3.1), tetapi di kedua edisi PowerShell di sana.

  • Karena perlu dijalankan sebagai user yang berbeda dan perlu untuk menangkap output, .WindowStyletidak dapat digunakan untuk menjalankan perintah tersembunyi (karena menggunakan .WindowStylemembutuhkan .UseShellExecuteuntuk menjadi $true, yang tidak sesuai dengan persyaratan ini); Namun, karena semua output ditangkap , pengaturan .CreateNoNewWindowuntuk $truesecara efektif menghasilkan eksekusi tersembunyi.

# 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
  # ([email protected]), 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

Di atas menghasilkan sesuatu seperti berikut, menunjukkan bahwa proses berhasil berjalan dengan identitas pengguna yang diberikan:

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

Karena Anda memanggil instance PowerShell lain , Anda mungkin ingin memanfaatkan kemampuan PowerShell CLI untuk merepresentasikan output dalam format CLIXML, yang memungkinkan deserialisasi output menjadi objek kaya , meskipun dengan ketelitian jenis terbatas , seperti dijelaskan dalam jawaban terkait ini .

# 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
  # ([email protected]), 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

Output di atas menghasilkan sesuatu seperti yang berikut, menunjukkan bahwa [datetime]instance ( System.DateTime) output oleh Get-Datedeserialized seperti:

Running `Get-Date` as user jdoe yielded:

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

as data type:
System.DateTime
mklement0
sumber
5

Start-Processakan menjadi pilihan terakhir saya untuk menggunakan PowerShell dari PowerShell - terutama karena semua I / O menjadi string dan bukan objek (deserialized).

Dua alternatif:

1. Jika pengguna adalah admin lokal dan PSRemoting dikonfigurasi

Jika sesi jarak jauh terhadap mesin lokal (sayangnya terbatas pada admin lokal) adalah pilihan, saya pasti akan mengikuti Invoke-Command:

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

$strings akan berisi hasil.


2. Jika pengguna bukan admin pada sistem target

Anda dapat menulis sendiri "hanya-lokal Invoke-Command" dengan memutar runspace out-of-proses dengan:

  1. Membuat PowerShellProcessInstance, di bawah login yang berbeda
  2. Membuat runspace dalam proses tersebut
  3. Jalankan kode Anda di runspace out-of-proses

Saya mengumpulkan fungsi seperti di bawah ini, lihat komentar inline untuk walk-through:

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()
            }
        }
    }
}

Kemudian gunakan seperti ini:

$output = Invoke-RunAs -FilePath C:\path\to\script1.ps1 -Credential $targetUser -NamedArguments @{ClientDevice = "ClientName"}
Mathias R. Jessen
sumber
0

rcv.ps1

param(
    $username,
    $password
)

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

eksekusi dari skrip lain:

.\rcv.ps1 'user' 'supersecretpassword'

keluaran:

The user is:  user
My super secret password is:  supersecretpassword
thepip3r
sumber
1
Saya harus memberikan kredensial ke exectuion ini ...
Dmytro
memperbarui bagian yang relevan.
thepip3r
Untuk mengklarifikasi: maksudnya bukan hanya untuk lulus kredensial, tetapi untuk dijalankan sebagai pengguna yang diidentifikasi oleh kredensial.
mklement0
1
@ mklement0, terima kasih atas klarifikasi karena itu tidak jelas sama sekali bagi saya oleh iterasi yang berbeda dari pertanyaan yang diajukan.
thepip3r
0

Apa yang dapat Anda lakukan berikut ini untuk meneruskan parameter ke skrip ps1.

Script pertama dapat berupa origin.ps1 tempat kami menulis:

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

Script tujuan dest.ps1 dapat memiliki kode berikut untuk menangkap variabel

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

Dan hasilnya akan seperti itu

my args Pa$$w0rd, parameter_a, parameter_n
Andy McRae
sumber
1
Tujuan utamanya adalah menggabungkan semua kondisi menjadi 1 eksekusi. Saya harus lulus parameter dan lulus kredensial!
Dmytro
Apa yang Anda maksud dengan "menggabungkan semua kondisi menjadi 1 eksekusi". Saya tidak berpikir Anda dapat menambahkan parameter dengan simbol "-" seperti yang Anda lakukan .. Saya pikir Anda harus memformat ulang string pada skrip tujuan
Andy McRae
Saya harus menjalankan beberapa file PS1 dengan parameter dan meneruskan -Credential $credentialsparameter ke eksekusi ini dan mendapatkan output dari itu menjadi variabel. Ps1. Script im executing adalah melemparkan string kata menghanguskan di akhir. Lihat saja cara saya melakukannya Start-processtetapi fungsi ini tidak menghasilkan keluaran
Dmytro
Saya pikir PowerShell tidak memungkinkan Anda untuk melewati parameter seperti ini $ arguments = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName. Anda mungkin harus berpikir untuk menghapus "-"
Andy McRae
1
kata beeing itu. Start-Process menjalankan script dengan parameter dan kredensial, tetapi tidak menyimpan output ini ke dalam variabel. Jika saya mencoba mengakses $outputvariabel, itu NULL. Ide lain yang datang dari @ mklement0 adalah untuk menyimpan output ke file. Tetapi dalam kasus saya ini akan menyebabkan sejumlah besar file di satu tempat. Semua dibuat dari pengguna yang berbeda dengan skrip yang berbeda
Dmytro