매개 변수 및 자격 증명으로 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.ProcessSystem.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.