powershellrunasstart-process

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!


Solution

  • Note:

    Here's a proof-of-concept based on direct use of the System.Diagnostics.Process and System.Diagnostics.ProcessStartInfo .NET types to capture process output in memory (as stated in your question, Start-Process is not an option, because it only supports capturing output in files, as shown in this answer):

    Note:

    # 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
    

    The above yields something like the following, showing that the process successfully ran with the given user identity:

    Running `cmd /c echo %USERNAME%` as user jdoe yielded:
    jdoe
    

    Since you're calling another PowerShell instance, you may want to take advantage of the PowerShell CLI's ability to represent output in CLIXML format, which allows deserializing the output into rich objects, albeit with limited type fidelity, as explained in this related answer.

    # 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
    

    The above outputs something like the following, showing that the [datetime] instance (System.DateTime) output by Get-Date was deserialized as such:

    Running `Get-Date` as user jdoe yielded:
    
    Friday, March 27, 2020 6:26:49 PM
    
    as data type:
    System.DateTime