Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
372 views
in Technique[技术] by (71.8m points)

runas - Starting .ps1 Script from PowerShell with Parameters and Credentials and getting output using variable

Hello Stack Community :)

I have a simple goal. I'd like to start some PowerShell Script from an another Powershell Script, but there are 3 conditions:

  1. I have to pass credentials (the execution connects to a database that has specific user)
  2. It has to take some Parameters
  3. I'd like to pass the output into a variable

There is a similar question Link. But the answer is to use files as a way to communicate between 2 PS Scripts. I just would like to avoid access conflicts. @Update: The Main Script is going to start few other scripts. so the solution with files can be tricky, if the execution will be performed from multiple user at the same time.

Script1.ps1 is the script that should have string as an output. (Just to be clear, it's a fictive script, the real one has 150 Rows, so I just wanted to make an example)

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

ExecuteScripts.ps1 should invoke that one with those 3 conditions mentioned above

I tried multiple solutions. This one for examplte:

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

I don't get any output from that and I can't just call the script with

&C:..script1.ps1 -ClientName PCPC

Because I can't pass -Credential parameter to it..

Thank you in Advance!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Start-Process would be my last resort choice for invoking PowerShell from PowerShell - especially because all I/O becomes strings and not (deserialized) objects.

Two alternatives:

1. If the user is a local admin and PSRemoting is configured

If a remote session against the local machine (unfortunately restricted to local admins) is a option, I'd definitely go with Invoke-Command:

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

$strings will contain the results.


2. If the user is not an admin on the target system

You can write your own "local-only Invoke-Command" by spinning up an out-of-process runspace by:

  1. Creating a PowerShellProcessInstance, under a different login
  2. Creating a runspace in said process
  3. Execute your code in said out-of-process runspace

I put together such a function below, see inline comments for a walk-through:

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

Then use like so:

$output = Invoke-RunAs -FilePath C:pathoscript1.ps1 -Credential $targetUser -NamedArguments @{ClientDevice = "ClientName"}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...