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:
- Creating a
PowerShellProcessInstance
, under a different login
- Creating a runspace in said process
- 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"}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…