パラメーターと資格情報を使用してPowerShellから.ps1スクリプトを開始し、変数を使用して出力を取得する


10

こんにちはスタックコミュニティ:)

単純な目標があります。別のPowershellスクリプトからPowerShellスクリプトを開始したいのですが、3つの条件があります。

  1. 資格情報を渡す必要があります(実行は特定のユーザーがいるデータベースに接続します)
  2. それはいくつかのパラメータを取る必要があります
  3. 出力を変数に渡したい

同様の質問リンクがあります。しかし、答えは、2つのPSスクリプト間で通信する方法としてファイルを使用することです。アクセスの競合を回避したいだけです。@更新:メインスクリプトは他のいくつかのスクリプトを開始します。そのため、複数のユーザーから同時に実行される場合、ファイルを使用したソリューションは扱いにくい場合があります。

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.ジェッセンの役に立つ答え使用して、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

上記の結果は次のようになり、指定されたユーザー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.DateTimeGet-Dateがそのように逆シリアル化されたことを示しています。

Running `Get-Date` as user jdoe yielded:

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

as data type:
System.DateTime

5

Start-Process私のだろう、最後の選択すべてのI / Oが文字列ではなく(非直列化)オブジェクトとなり、特にために- PowerShellのからPowerShellを起動します。

2つの選択肢:

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
私はこの実行に資格を渡す必要があります...
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
その蜂は言った。Start-Processはパラメーターと資格情報を使用してスクリプトを実行しますが、この出力を変数に保存しません。$output変数にアクセスしようとしている場合、それはNULLです。@ mklement0から得られたもう1つのアイデアは、出力をファイルに保存することです。しかし、私の場合は、1か所に大量のファイルが作成されます。すべて異なるスクリプトを使用して異なるユーザーから作成
Dmytro
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.