매개 변수 및 자격 증명으로 PowerShell에서 .ps1 스크립트 시작 및 변수를 사용하여 출력 얻기


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 가지 조건을 가진 것을 호출해야합니다

여러 솔루션을 시도했습니다. 시험에 대한 하나 :

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

나는 그 결과를 얻지 못하고 스크립트를 호출 할 수 없다.

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

-Credential매개 변수를 전달할 수 없기 때문에 ..

미리 감사드립니다!


이것이 액세스 충돌에 관한 것이라면 : 모든 호출에 대해 고유 한 파일 이름 을 생성 하면 문제가 해결 될 것입니다.
— mklement0

1
그것이 유일한 방법이라면 @ mklement0, 그 솔루션으로 쌓을 것입니다. 임의의 파일 이름을 생성하고 해당 파일이 존재하는지 확인하는 중 ... Java 코드에서 6 ~ 10 개의 스크립트를 실행하며 사용 중이거나 다른 사람이 내 응용 프로그램을 사용할 때마다 6 ~ 10 개의 파일이 필요합니다. 따라서 성능에 대해서도
— Dmytro

답변:


2

노트 :

  • 다음 솔루션 은 모든 외부 프로그램 에서 작동하며 항상 텍스트 로 출력을 캡처 합니다 .

  • 하기 위해 호출하는 다른 PowerShell을 인스턴스 및 출력 캡처 풍부한 개체로 (제한 사항)을, 하단 부분의 변형 솔루션을 참조하거나 고려 마티아스 R. Jessen의 도움이 대답 용도, PowerShell을 SDK를 .

다음은이 개념 증명을 기반으로 의 직접 사용 System.Diagnostics.Process및 System.Diagnostics.ProcessStartInfo캡처 프로세스의 출력 .NET 종류의 메모리 (귀하의 질문에 명시된 바와 같이, Start-Process그것은 단지 출력을 캡처 지원하기 때문에, 옵션이 아닌 파일 에서와 같이, 이 답변 ) :

노트 :

  • 다른 사용자로 실행하기 때문에이 기능은 Windows (.NET Core 3.1 기준) 에서만 지원 되지만 두 PowerShell 버전 모두에서 지원됩니다 .

  • 다른 사용자로 실행해야하고 출력을 캡처 해야 하므로 .WindowStyle명령을 숨김 으로 실행하는 데 사용할 수 없습니다 ( .WindowStylerequires .UseShellExecute는 be 가 필요 $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

위의 결과는 다음과 같이 산출되며 주어진 사용자 ID로 프로세스가 성공적으로 실행되었음을 나타냅니다.

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-ProcessPowerShell에서 PowerShell을 호출하는 마지막 방법 은 특히 모든 I / O가 (직렬화되지 않은) 개체가 아닌 문자열이되기 때문입니다.

두 가지 대안 :

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
나는이 실행에 credentails를 전달해야합니다 ...
— 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
주요 목표는 모든 조건을 하나의 실행으로 결합하는 것입니다. 매개 변수를 전달하고 자격 증명을 전달해야합니다!
— Dmytro

"모든 조건을 하나의 실행으로 결합"이란 무엇입니까? 난 당신이 대상 스크립트의 문자열 포맷한다고 생각 .. 당신이했던 것처럼 상징 - ""난 당신이 가진 매개 변수를 추가 할 수 있다고 생각 해달라고
— 앤디 맥레이

매개 변수가있는 일부 PS1 파일을 -Credential $credentials실행 하고이 실행에 매개 변수를 전달하고 변수에서 출력해야합니다. ps1. 실행중인 스크립트 메신저가 끝에 단일 단어 문자열을 던지고 있습니다. 내가하는 방식을 살펴보면 Start-process이 함수는 출력을 생성하지 않습니다
— Dmytro

powershell에서는 $ arguments = "C : \ .. \ script1.ps1"+ "-ClientName"+ $ DeviceName과 같은 매개 변수를 전달할 수 없습니다. "-"
— Andy McRae

1
그 꿀벌이 말했다. Start-Process는 매개 변수 및 자격 증명을 사용하여 스크립트를 실행하지만이 출력을 변수에 저장하지 않습니다. $output변수 에 액세스하려고하면 NULL입니다. @ mklement0에서 나온 다른 아이디어는 출력을 파일에 저장하는 것입니다. 그러나 제 경우에는 한곳에서 엄청난 양의 파일이 발생합니다. 다른 스크립트를 사용하여 다른 사용자가 만든 모든
— Dmytro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.