เริ่มต้น. ps1 สคริปต์จาก PowerShell ด้วยพารามิเตอร์และข้อมูลรับรองและรับเอาต์พุตโดยใช้ตัวแปร


10

สวัสดีชุมชนกอง :)

ฉันมีเป้าหมายง่าย ๆ ฉันต้องการเริ่ม PowerShell Script จาก Powershell Script อีกอัน แต่มี 3 เงื่อนไข:

  1. ฉันต้องผ่านการรับรอง (การดำเนินการเชื่อมต่อกับฐานข้อมูลที่มีผู้ใช้เฉพาะ)
  2. ต้องใช้พารามิเตอร์บางตัว
  3. ฉันต้องการส่งออกเป็นตัวแปร

มีคำถามที่คล้ายกันคือการเชื่อมโยง แต่คำตอบก็คือใช้ไฟล์เป็นวิธีการสื่อสารระหว่าง 2 PS Scripts ฉันต้องการหลีกเลี่ยงความขัดแย้งในการเข้าถึง @ อัปเดต: สคริปต์หลักกำลังจะเริ่มต้นสคริปต์อื่น ๆ ดังนั้นการแก้ปัญหาด้วยไฟล์อาจเป็นเรื่องยากหากการดำเนินการจะดำเนินการจากผู้ใช้หลายคนพร้อมกัน

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 ตัวจากจาวาโค้ดของฉันและมันจะต้องมีไฟล์ 6 ถึง 10 ไฟล์ทุกครั้งที่ฉันใช้งานหรือมีคนอื่นใช้แอพพลิเคชันของฉัน ดังนั้นเกี่ยวกับการแสดงด้วย
Dmytro

คำตอบ:


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 - โดยเฉพาะอย่างยิ่งเนื่องจาก I / O ทั้งหมดกลายเป็นสตริงและไม่ใช่วัตถุ (ดีซีเรียลไลซ์)

สองทางเลือก:

1. หากผู้ใช้เป็นผู้ดูแลท้องถิ่นและมีการกำหนดค่า PSRemoting

หากเซสชันระยะไกลเทียบกับเครื่องท้องถิ่น (น่าเสียดายที่ จำกัด เฉพาะผู้ดูแลระบบในพื้นที่) เป็นตัวเลือกฉันจะไปกับInvoke-Command:

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

$strings จะมีผลลัพธ์


2. หากผู้ใช้ไม่ใช่ผู้ดูแลระบบเป้าหมาย

คุณสามารถเขียน "local-only Invoke-Command" ของคุณเองโดยหมุนเวิร์กสเปซนอกกระบวนการโดย:

  1. การสร้างPowerShellProcessInstanceภายใต้การเข้าสู่ระบบที่แตกต่างกัน
  2. การสร้าง runspace ในกระบวนการดังกล่าว
  3. รันโค้ดของคุณใน runspace ที่ไม่อยู่ระหว่างดำเนินการ

ฉันได้รวมฟังก์ชั่นด้านล่างไว้ด้วยกันดูความคิดเห็นแบบอินไลน์สำหรับการเดินผ่าน:

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
ฉันต้องส่งเรื่องไปที่ exectuion นี้ ...
Dmytro

ปรับปรุงส่วนที่เกี่ยวข้อง
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 การดำเนินการ ฉันต้องผ่านพารามิเตอร์และผ่านการรับรอง!
Dmytro

คุณหมายความว่าอย่างไรกับ "รวมเงื่อนไขทั้งหมดเป็น 1 การดำเนินการ" ฉันไม่คิดว่าคุณสามารถเพิ่มพารามิเตอร์ด้วยสัญลักษณ์ "-" อย่างที่คุณทำ .. ฉันคิดว่าคุณต้องฟอร์แมตสตริงใหม่ในสคริปต์ปลายทาง
Andy McRae

ฉันต้องเรียกใช้งานไฟล์ PS1 บางตัวพร้อมพารามิเตอร์และส่ง-Credential $credentialsพารามิเตอร์ไปยังการดำเนินการนี้และรับเอาต์พุตจากไฟล์นั้นเป็นตัวแปร ps1 สคริปต์ที่กำลังดำเนินการกำลังขว้างสตริงคำเดียวในตอนท้าย ลองดูวิธีที่ฉันใช้ด้วยStart-processแต่ฟังก์ชั่นนี้จะไม่สร้างเอาต์พุต
Dmytro

ฉันคิดว่า powershell ไม่อนุญาตให้คุณผ่านพารามิเตอร์เช่นนี้ $ arguments = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName คุณควรนึกถึงการลบ "-"
Andy McRae

1
ผึ้งพูดอย่างนั้น เริ่มกระบวนการจะรันสคริปต์ด้วยพารามิเตอร์และข้อมูลประจำตัวบิตมันไม่ได้บันทึกผลลัพธ์นี้ลงในตัวแปร ถ้าฉันพยายามที่จะเข้าถึง$outputตัวแปรมันเป็นโมฆะ แนวคิดอื่นที่มาจาก @ mklement0 คือการบันทึกผลลัพธ์ไปยังไฟล์ แต่ในกรณีของฉันมันจะทำให้เกิดไฟล์จำนวนมากในที่เดียว ทั้งหมดสร้างจากผู้ใช้ที่แตกต่างกันและมีสคริปต์ต่างกัน
Dmytro
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.