Bắt đầu .ps1 Script từ PowerShell với Tham số và Thông tin xác thực và nhận đầu ra bằng biến


10

Xin chào cộng đồng Stack :)

Tôi có một mục tiêu đơn giản. Tôi muốn bắt đầu một số PowerShell Script từ một Powershell Script khác, nhưng có 3 điều kiện:

  1. Tôi phải chuyển thông tin đăng nhập (việc thực thi kết nối với cơ sở dữ liệu có người dùng cụ thể)
  2. Nó phải mất một số tham số
  3. Tôi muốn chuyển đầu ra thành một biến

Có một câu hỏi tương tự Liên kết . Nhưng câu trả lời là sử dụng các tệp như một cách để giao tiếp giữa 2 Tập lệnh PS. Tôi chỉ muốn tránh xung đột truy cập. @Update: Tập lệnh chính sẽ bắt đầu một vài tập lệnh khác. do đó, giải pháp với các tệp có thể khó, nếu việc thực thi sẽ được thực hiện từ nhiều người dùng cùng một lúc.

Script1.ps1 là tập lệnh nên có chuỗi làm đầu ra. (Nói rõ hơn, đó là một kịch bản hư cấu, kịch bản thật có 150 Hàng, vì vậy tôi chỉ muốn làm một ví dụ)

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

ExecuteScripts.ps1 sẽ gọi một trong 3 điều kiện được đề cập ở trên

Tôi đã thử nhiều giải pháp. Cái này dành cho kỳ thi:

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

Tôi không nhận được bất kỳ đầu ra nào từ đó và tôi không thể gọi kịch bản với

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

Bởi vì tôi không thể truyền -Credentialtham số cho nó ..

Cảm ơn bạn trước!


Nếu đây chỉ là về xung đột truy cập: tạo tên tệp duy nhất cho mỗi lần gọi sẽ giải quyết vấn đề của bạn, phải không?
mkuity0

1
@ mkuity0 nếu đó là cách duy nhất, tôi sẽ xếp chồng với giải pháp đó. Chỉ cần tạo tên tệp ngẫu nhiên, kiểm tra xem tệp đó có tồn tại không ... Tôi sẽ thực thi 6 đến 10 tập lệnh từ Mã Java của mình và nó sẽ cần 6 đến 10 tệp mỗi khi tôi đang sử dụng hoặc người khác sử dụng ứng dụng của tôi. Vì vậy, đó là về hiệu suất cũng
Dmytro

Câu trả lời:


2

Ghi chú:

  • Giải pháp sau đây hoạt động với bất kỳ chương trình bên ngoài nàothu được kết quả đầu ra dưới dạng văn bản .

  • Để gọi một phiên bản PowerShell khácnắm bắt đầu ra của nó dưới dạng các đối tượng phong phú (có giới hạn), hãy xem giải pháp biến thể trong phần dưới cùng hoặc xem xét câu trả lời hữu ích của Mathias R. Jessen , sử dụng SDK PowerShell .

Đây là một bằng chứng khái niệm dựa trên việc sử dụng trực tiếp các loại System.Diagnostics.ProcessSystem.Diagnostics.ProcessStartInfo.NET để nắm bắt đầu ra quá trình trong bộ nhớ (như đã nêu trong câu hỏi của bạn, Start-Processkhông phải là một tùy chọn, vì nó chỉ hỗ trợ chụp đầu ra trong các tệp , như trong câu trả lời này ) :

Ghi chú:

  • Do chạy như một người dùng khác, điều này chỉ được hỗ trợ trên Windows (kể từ .NET Core 3.1), nhưng trong cả hai phiên bản PowerShell đều có.

  • Do cần phải chạy như một người dùng khác nhau và cần phải đầu ra chụp, .WindowStylekhông thể được sử dụng để chạy các lệnh ẩn (vì sử dụng .WindowStyleđòi hỏi .UseShellExecutephải $true, đó là không phù hợp với các yêu cầu này); tuy nhiên, vì tất cả đầu ra đang bị bắt , thiết lập .CreateNoNewWindowđể $truecó kết quả thực hiện ẩn.

# 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

Những điều trên mang lại một cái gì đó như sau, cho thấy quá trình đã chạy thành công với danh tính người dùng nhất định:

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

bạn đang gọi một phiên bản PowerShell khác , bạn có thể muốn tận dụng khả năng của PowerShell CLI để thể hiện đầu ra ở định dạng CLIXML, cho phép khử lưu lượng đầu ra thành các đối tượng phong phú , mặc dù có độ trung thực loại hạn chế , như được giải thích trong câu trả lời liên quan này .

# 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

Các đầu ra ở trên giống như sau, cho thấy đầu ra của [datetime]thể hiện ( System.DateTime) Get-Dateđược khử lưu như vậy:

Running `Get-Date` as user jdoe yielded:

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

as data type:
System.DateTime

5

Start-Processsẽ là lựa chọn cuối cùng của tôi để gọi PowerShell từ PowerShell - đặc biệt là vì tất cả I / O trở thành chuỗi và không phải là đối tượng (được khử lưu).

Hai lựa chọn thay thế:

1. Nếu người dùng là quản trị viên cục bộ và PSRemote được định cấu hình

Nếu một phiên từ xa đối với máy cục bộ (không may bị giới hạn ở quản trị viên cục bộ) là một tùy chọn, tôi chắc chắn sẽ sử dụng Invoke-Command:

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

$strings sẽ chứa kết quả.


2. Nếu người dùng không phải là quản trị viên trên hệ thống đích

Bạn có thể viết "chỉ cục bộ" của riêng mình Invoke-Commandbằng cách tạo ra một không gian chạy ngoài quy trình bằng cách:

  1. Tạo một PowerShellProcessInstance, dưới một đăng nhập khác
  2. Tạo một không gian trong quy trình đã nói
  3. Thực thi mã của bạn trong không gian chạy ngoài quy trình đã nói

Tôi kết hợp một chức năng như vậy bên dưới, xem các bình luận nội tuyến để xem qua:

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

Sau đó sử dụng như vậy:

$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"

thực hiện từ một kịch bản khác:

.\rcv.ps1 'user' 'supersecretpassword'

đầu ra:

The user is:  user
My super secret password is:  supersecretpassword

1
Tôi phải chuyển tín dụng cho ngoại lệ này ...
Dmytro

cập nhật các phần có liên quan.
thepip3r

Để làm rõ: mục đích không chỉ là thông qua thông tin đăng nhập, mà còn chạy khi người dùng được xác định bởi thông tin đăng nhập.
mkuity0

1
@ mkuity0, cảm ơn bạn đã làm rõ vì điều đó không rõ ràng với tôi bởi các lần lặp khác nhau của câu hỏi đang được hỏi.
thepip3r

0

Những gì bạn có thể làm như sau để truyền tham số cho tập lệnh ps1.

Kịch bản đầu tiên có thể là một origin.ps1 nơi chúng ta viết:

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

Tập lệnh đích Dest.ps1 có thể có đoạn mã sau để nắm bắt các biến

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

Và kết quả sẽ là

my args Pa$$w0rd, parameter_a, parameter_n

1
Mục tiêu chính là kết hợp tất cả các điều kiện thành 1 thực thi. Tôi phải vượt qua các tham số và để vượt qua thông tin đăng nhập!
Dmytro

Bạn có ý nghĩa gì với "kết hợp tất cả các điều kiện thành 1 thực thi". Tôi không nghĩ bạn có thể thêm một tham số với biểu tượng "-" như bạn đã làm .. Tôi nghĩ bạn phải định dạng lại chuỗi trên tập lệnh đích
Andy McRae

Tôi phải thực thi một số tệp PS1 với các tham số và truyền -Credential $credentialstham số cho thực thi này và nhận đầu ra từ nó thành một biến. Các ps1. Script đang thực thi là ném một chuỗi từ singe ở cuối. Chỉ cần nhìn vào cách tôi đã làm với Start-processnhưng chức năng này không tạo ra đầu ra
Dmytro

Tôi nghĩ powershell không cho phép bạn truyền tham số như $ argument = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName. Có lẽ bạn nên nghĩ đến việc xóa "-"
Andy McRae

1
mà ong nói. Start-Process thực thi tập lệnh với các tham số và thông tin đăng nhập, bit nó không lưu kết quả đầu ra này vào một biến. Nếu tôi đang cố gắng truy cập $outputbiến, đó là NULL. Ý tưởng khác xuất phát từ @ mkuity0 là lưu kết quả đầu ra vào một tệp. Nhưng trong trường hợp của tôi, nó sẽ gây ra một lượng lớn tệp ở một nơi. Tất cả được tạo từ những người dùng khác nhau với các tập lệnh khác nhau
Dmytro
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.