mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add 5 'InvokeAsync' overloads and 'StopAsync' to the 'PowerShell' type (#8056)
This commit is contained in:
+50
@@ -280,6 +280,10 @@ namespace System.Management.Automation.Runspaces
|
||||
yield return new ExtendedTypeDefinition(
|
||||
"Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstance",
|
||||
ViewsOf_Microsoft_Management_Infrastructure_CimInstance___PartialCIMInstance());
|
||||
|
||||
yield return new ExtendedTypeDefinition(
|
||||
"System.Threading.Tasks.Task",
|
||||
ViewsOf_System_Threading_Tasks_Task());
|
||||
}
|
||||
|
||||
private static IEnumerable<FormatViewDefinition> ViewsOf_System_CodeDom_Compiler_CompilerError()
|
||||
@@ -1709,5 +1713,51 @@ namespace System.Management.Automation.Runspaces
|
||||
.EndEntry()
|
||||
.EndControl());
|
||||
}
|
||||
|
||||
private static IEnumerable<FormatViewDefinition> ViewsOf_System_Threading_Tasks_Task()
|
||||
{
|
||||
// Avoid referencing the Result property in these views to avoid potential
|
||||
// deadlocks that may occur. Result should only be referenced once the task
|
||||
// is actually completed.
|
||||
yield return new FormatViewDefinition(
|
||||
"System.Threading.Tasks.Task",
|
||||
TableControl
|
||||
.Create()
|
||||
.AddHeader(label: "Id")
|
||||
.AddHeader(label: "IsCompleted")
|
||||
.AddHeader(label: "Status")
|
||||
.StartRowDefinition()
|
||||
.AddPropertyColumn("Id")
|
||||
.AddPropertyColumn("IsCompleted")
|
||||
.AddPropertyColumn("Status")
|
||||
.EndRowDefinition()
|
||||
.EndTable());
|
||||
|
||||
yield return new FormatViewDefinition(
|
||||
"System.Threading.Tasks.Task",
|
||||
ListControl
|
||||
.Create()
|
||||
.StartEntry()
|
||||
.AddItemProperty(@"AsyncState")
|
||||
.AddItemProperty(@"AsyncWaitHandle")
|
||||
.AddItemProperty(@"CompletedSynchronously")
|
||||
.AddItemProperty(@"CreationOptions")
|
||||
.AddItemProperty(@"Exception")
|
||||
.AddItemProperty(@"Id")
|
||||
.AddItemProperty(@"IsCanceled")
|
||||
.AddItemProperty(@"IsCompleted")
|
||||
.AddItemProperty(@"IsCompletedSuccessfully")
|
||||
.AddItemProperty(@"IsFaulted")
|
||||
.AddItemScriptBlock(
|
||||
@"
|
||||
if ($_.IsCompleted) {
|
||||
$_.Result
|
||||
}
|
||||
",
|
||||
label: "Result")
|
||||
.AddItemProperty(@"Status")
|
||||
.EndEntry()
|
||||
.EndList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Management.Automation.Runspaces;
|
||||
using System.Management.Automation.Runspaces.Internal;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Management.Infrastructure;
|
||||
using Dbg = System.Management.Automation.Diagnostics;
|
||||
|
||||
@@ -598,6 +599,10 @@ namespace System.Management.Automation
|
||||
private bool _isBatching = false;
|
||||
private bool _stopBatchExecution = false;
|
||||
|
||||
// Delegates for asynchronous invocation/termination of PowerShell commands
|
||||
private readonly Func<IAsyncResult, PSDataCollection<PSObject>> _endInvokeMethod;
|
||||
private readonly Action<IAsyncResult> _endStopMethod;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
@@ -633,6 +638,8 @@ namespace System.Management.Automation
|
||||
ErrorBufferOwner = true;
|
||||
InformationalBuffers = new PSInformationalBuffers(InstanceId);
|
||||
Streams = new PSDataStreams(this);
|
||||
_endInvokeMethod = EndInvoke;
|
||||
_endStopMethod = EndStop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -695,6 +702,9 @@ namespace System.Management.Automation
|
||||
{
|
||||
RemotePowerShell = new ClientRemotePowerShell(this, runspacePool.RemoteRunspacePoolInternal);
|
||||
}
|
||||
|
||||
_endInvokeMethod = EndInvoke;
|
||||
_endStopMethod = EndStop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -3057,6 +3067,226 @@ namespace System.Management.Automation
|
||||
return CoreInvokeAsync<TInput, TOutput>(input, output, settings, callback, state, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a PowerShell command asynchronously.
|
||||
/// Use await to wait for the command to complete and obtain the output of the command.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The output buffer created to hold the results of the asynchronous invoke.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Cannot perform the operation because the command is already started.
|
||||
/// Stop the command and try the operation again.
|
||||
/// (or)
|
||||
/// No command is added.
|
||||
/// </exception>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Object is disposed.
|
||||
/// </exception>
|
||||
public async Task<PSDataCollection<PSObject>> InvokeAsync()
|
||||
{
|
||||
return await Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke(), _endInvokeMethod).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a PowerShell command asynchronously.
|
||||
/// Use await to wait for the command to complete and obtain the output of the command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When invoked using InvokeAsync, invocation doesn't
|
||||
/// finish until Input is closed. Caller of InvokeAsync must
|
||||
/// close the input buffer after all input has been written to
|
||||
/// input buffer. Input buffer is closed by calling
|
||||
/// Close() method.
|
||||
/// </para><para>
|
||||
/// If you want this command to execute as a standalone cmdlet
|
||||
/// (that is, using command-line parameters only),
|
||||
/// be sure to call Close() before calling InvokeAsync(). Otherwise,
|
||||
/// the command will be executed as though it had external input.
|
||||
/// If you observe that the command isn't doing anything,
|
||||
/// this may be the reason.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">
|
||||
/// Type of the input buffer.
|
||||
/// </typeparam>
|
||||
/// <param name="input">
|
||||
/// Input to the command. See remarks for more details.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The output buffer created to hold the results of the asynchronous invoke.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Cannot perform the operation because the command is already started.
|
||||
/// Stop the command and try the operation again.
|
||||
/// (or)
|
||||
/// No command is added.
|
||||
/// </exception>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Object is disposed.
|
||||
/// </exception>
|
||||
public async Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input)
|
||||
=> await Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<T>(input), _endInvokeMethod).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a PowerShell command asynchronously.
|
||||
/// Use await to wait for the command to complete and obtain the output of the command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When invoked using InvokeAsync, invocation doesn't
|
||||
/// finish until Input is closed. Caller of InvokeAsync must
|
||||
/// close the input buffer after all input has been written to
|
||||
/// input buffer. Input buffer is closed by calling
|
||||
/// Close() method.
|
||||
/// </para><para>
|
||||
/// If you want this command to execute as a standalone cmdlet
|
||||
/// (that is, using command-line parameters only),
|
||||
/// be sure to call Close() before calling InvokeAsync(). Otherwise,
|
||||
/// the command will be executed as though it had external input.
|
||||
/// If you observe that the command isn't doing anything,
|
||||
/// this may be the reason.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">
|
||||
/// Type of the input buffer.
|
||||
/// </typeparam>
|
||||
/// <param name="input">
|
||||
/// Input to the command. See remarks for more details.
|
||||
/// </param>
|
||||
/// <param name="settings">
|
||||
/// Invocation Settings.
|
||||
/// </param>
|
||||
/// <param name="callback">
|
||||
/// An AsyncCallback to call once the command is invoked.
|
||||
/// </param>
|
||||
/// <param name="state">
|
||||
/// A user supplied state to call the <paramref name="callback"/>
|
||||
/// with.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The output buffer created to hold the results of the asynchronous invoke.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Cannot perform the operation because the command is already started.
|
||||
/// Stop the command and try the operation again.
|
||||
/// (or)
|
||||
/// No command is added.
|
||||
/// </exception>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Object is disposed.
|
||||
/// </exception>
|
||||
public async Task<PSDataCollection<PSObject>> InvokeAsync<T>(PSDataCollection<T> input, PSInvocationSettings settings, AsyncCallback callback, object state)
|
||||
=> await Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<T>(input, settings, callback, state), _endInvokeMethod).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a PowerShell command asynchronously.
|
||||
/// Use await to wait for the command to complete and obtain the output of the command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When invoked using InvokeAsync, invocation doesn't
|
||||
/// finish until Input is closed. Caller of InvokeAsync must
|
||||
/// close the input buffer after all input has been written to
|
||||
/// input buffer. Input buffer is closed by calling
|
||||
/// Close() method.
|
||||
/// </para><para>
|
||||
/// If you want this command to execute as a standalone cmdlet
|
||||
/// (that is, using command-line parameters only),
|
||||
/// be sure to call Close() before calling InvokeAsync(). Otherwise,
|
||||
/// the command will be executed as though it had external input.
|
||||
/// If you observe that the command isn't doing anything,
|
||||
/// this may be the reason.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="TInput">
|
||||
/// Type of input object(s) for the command invocation.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TOutput">
|
||||
/// Type of output object(s) expected from the command invocation.
|
||||
/// </typeparam>
|
||||
/// <param name="input">
|
||||
/// Input to the command. See remarks for more details.
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// A buffer supplied by the user where output is collected.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The output buffer created to hold the results of the asynchronous invoke,
|
||||
/// or null if the caller provided their own buffer.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Cannot perform the operation because the command is already started.
|
||||
/// Stop the command and try the operation again.
|
||||
/// (or)
|
||||
/// No command is added.
|
||||
/// </exception>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Object is disposed.
|
||||
/// </exception>
|
||||
public async Task<PSDataCollection<PSObject>> InvokeAsync<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output)
|
||||
=> await Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<TInput, TOutput>(input, output), _endInvokeMethod).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a PowerShell command asynchronously and collect
|
||||
/// output data into the buffer <paramref name="output"/>.
|
||||
/// Use await to wait for the command to complete and obtain the output of the command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When invoked using InvokeAsync, invocation doesn't
|
||||
/// finish until Input is closed. Caller of InvokeAsync must
|
||||
/// close the input buffer after all input has been written to
|
||||
/// input buffer. Input buffer is closed by calling
|
||||
/// Close() method.
|
||||
/// </para><para>
|
||||
/// If you want this command to execute as a standalone cmdlet
|
||||
/// (that is, using command-line parameters only),
|
||||
/// be sure to call Close() before calling InvokeAsync(). Otherwise,
|
||||
/// the command will be executed as though it had external input.
|
||||
/// If you observe that the command isn't doing anything,
|
||||
/// this may be the reason.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="TInput">
|
||||
/// Type of input object(s) for the command invocation.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TOutput">
|
||||
/// Type of output object(s) expected from the command invocation.
|
||||
/// </typeparam>
|
||||
/// <param name="input">
|
||||
/// Input to the command. See remarks for more details.
|
||||
/// </param>
|
||||
/// <param name="output">
|
||||
/// A buffer supplied by the user where output is collected.
|
||||
/// </param>
|
||||
/// <param name="settings">
|
||||
/// Invocation Settings.
|
||||
/// </param>
|
||||
/// <param name="callback">
|
||||
/// An AsyncCallback to call once the command is invoked.
|
||||
/// </param>
|
||||
/// <param name="state">
|
||||
/// A user supplied state to call the <paramref name="callback"/>
|
||||
/// with.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The output buffer created to hold the results of the asynchronous invoke,
|
||||
/// or null if the caller provided their own buffer.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Cannot perform the operation because the command is already started.
|
||||
/// Stop the command and try the operation again.
|
||||
/// (or)
|
||||
/// No command is added.
|
||||
/// </exception>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Object is disposed.
|
||||
/// </exception>
|
||||
public async Task<PSDataCollection<PSObject>> InvokeAsync<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, AsyncCallback callback, object state)
|
||||
=> await Task<PSDataCollection<PSObject>>.Factory.FromAsync(BeginInvoke<TInput, TOutput>(input, output, settings, callback, state), _endInvokeMethod).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Begins a batch execution.
|
||||
/// </summary>
|
||||
@@ -3554,6 +3784,35 @@ namespace System.Management.Automation
|
||||
ResetOutputBufferAsNeeded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop a PowerShell command asynchronously.
|
||||
/// Use await to wait for the command to stop.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If the command is not started, the state of the PowerShell instance
|
||||
/// is changed to Stopped and corresponding events will be raised.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="callback">
|
||||
/// An AsyncCallback to call once the command is invoked.
|
||||
/// </param>
|
||||
/// <param name="state">
|
||||
/// A user supplied state to call the <paramref name="callback"/>
|
||||
/// with.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The output buffer created to hold the results of the asynchronous invoke,
|
||||
/// or null if the caller provided their own buffer.
|
||||
/// </returns>
|
||||
/// <exception cref="ObjectDisposedException">
|
||||
/// Object is disposed.
|
||||
/// </exception>
|
||||
public async Task StopAsync(AsyncCallback callback, object state)
|
||||
{
|
||||
await Task.Factory.FromAsync(BeginStop(callback, state), _endStopMethod).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -725,7 +725,7 @@ dir -Recurse `
|
||||
It "Test member completion of a static method invocation" {
|
||||
$inputStr = '[powershell]::Create().'
|
||||
$res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length
|
||||
$res.CompletionMatches | Should -HaveCount 32
|
||||
$res.CompletionMatches | Should -HaveCount 34
|
||||
$res.CompletionMatches[0].CompletionText | Should -BeExactly "Commands"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
Describe 'Task-based PowerShell async APIs' -Tags 'CI' {
|
||||
BeforeAll {
|
||||
$sbStub = @'
|
||||
.foreach{
|
||||
[pscustomobject]@{
|
||||
Time = [DateTime]::Now.ToString('yyyyMMddTHHmmss.fffffff')
|
||||
Value = $_
|
||||
ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId
|
||||
RunspaceId = [runspace]::DefaultRunspace.Id
|
||||
}
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
'@
|
||||
}
|
||||
|
||||
Context 'PowerShell::InvokeAsync - Single script tests' {
|
||||
BeforeAll {
|
||||
function InvokeAsyncHelper {
|
||||
param(
|
||||
[powershell]$PowerShell,
|
||||
[switch]$Wait
|
||||
)
|
||||
$r = $PowerShell.AddScript("@(1,2,3,4,5)${sbStub}").InvokeAsync()
|
||||
if ($Wait) {
|
||||
[System.Threading.Tasks.Task]::WaitAll(@($r))
|
||||
}
|
||||
$r
|
||||
}
|
||||
|
||||
function GetInnerErrorId {
|
||||
param(
|
||||
[exception]$Exception
|
||||
)
|
||||
while ($null -ne $Exception.InnerException) {
|
||||
$Exception = $Exception.InnerException
|
||||
if ($null -ne (Get-Member -InputObject $Exception -Name ErrorRecord)) {
|
||||
$Exception.ErrorRecord.FullyQualifiedErrorId
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It 'can invoke a single script asynchronously, but only in a new runspace' {
|
||||
$ps = [powershell]::Create()
|
||||
try {
|
||||
$r = InvokeAsyncHelper -PowerShell $ps -Wait
|
||||
$r.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r.IsCompletedSuccessfully | Should -Be $true
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'handles terminating errors properly when invoked asynchronously in a new runspace' {
|
||||
$ps = [powershell]::Create()
|
||||
try {
|
||||
$sb = {
|
||||
$r = $ps.AddScript(@'
|
||||
try {
|
||||
Get-Process -Invalid 42
|
||||
} catch {
|
||||
throw
|
||||
}
|
||||
'@).InvokeAsync()
|
||||
Set-Variable -Name r -Scope 2 -Value $r
|
||||
[System.Threading.Tasks.Task]::WaitAll(@($r))
|
||||
$r
|
||||
}
|
||||
# This test is designed to gracefully fail with an error when invoked asynchronously.
|
||||
{ $sb.Invoke() } | Should -Throw -ErrorId 'AggregateException'
|
||||
$r.IsFaulted | Should -Be $true
|
||||
$r.Exception.InnerException -is [System.Management.Automation.ParameterBindingException] | Should -Be $true
|
||||
$r.Exception.InnerException.CommandInvocation.InvocationName | Should -BeExactly 'Get-Process'
|
||||
$r.Exception.InnerException.ParameterName | Should -BeExactly 'Invalid'
|
||||
$r.Exception.InnerException.ErrorId | Should -BeExactly 'NamedParameterNotFound'
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'cannot invoke a single script asynchronously in a runspace that has not been opened' {
|
||||
$rs = [runspacefactory]::CreateRunspace()
|
||||
$ps = [powershell]::Create($rs)
|
||||
try {
|
||||
$r = $ps.AddScript('@(1..10).foreach{Start-Sleep -Milliseconds 500}').InvokeAsync()
|
||||
# This test is designed to fail. You cannot invoke PowerShell asynchronously
|
||||
# in a runspace that has not been opened.
|
||||
$r.IsFaulted | Should -Be $true
|
||||
$r.Exception -is [System.AggregateException] | Should -Be $true
|
||||
$r.Exception.InnerException -is [System.Management.Automation.Runspaces.InvalidRunspaceStateException] | Should -Be $true
|
||||
$r.Exception.InnerException.CurrentState | Should -Be 'BeforeOpen'
|
||||
$r.Exception.InnerException.ExpectedState | Should -Be 'Opened'
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
$rs.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'cannot invoke a single script asynchronously in a runspace that is busy' {
|
||||
$ps = [powershell]::Create($Host.Runspace)
|
||||
try {
|
||||
# This test is designed to fail. You cannot invoke PowerShell asynchronously
|
||||
# in a runspace that is busy, because pipelines cannot be run concurrently.
|
||||
$err = { InvokeAsyncHelper -PowerShell $ps -Wait } | Should -Throw -ErrorId 'AggregateException' -PassThru
|
||||
GetInnerErrorId -Exception $err.Exception | Should -Be 'InvalidOperation'
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'cannot invoke a single script asynchronously in the current runspace' {
|
||||
$ps = [powershell]::Create('CurrentRunspace')
|
||||
try {
|
||||
# This test is designed to fail. You cannot invoke PowerShell asynchronously
|
||||
# in the current runspace because nested PowerShell instances cannot be
|
||||
# invoked asynchronously
|
||||
$err = { InvokeAsyncHelper -PowerShell $ps -Wait } | Should -Throw -ErrorId 'AggregateException' -PassThru
|
||||
GetInnerErrorId -Exception $err.Exception | Should -Be 'InvalidOperation'
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context 'PowerShell::InvokeAsync - Multiple script tests' {
|
||||
It 'can invoke multiple scripts asynchronously' {
|
||||
$ps1 = [powershell]::Create()
|
||||
$ps2 = [powershell]::Create()
|
||||
try {
|
||||
$r1 = $ps1.AddScript("@(1,3,5,7,9,11,13,15,17,19)${sbStub}").InvokeAsync()
|
||||
$r2 = $ps2.AddScript("@(2,4,6,8,10,12,14,16,18,20)${sbStub}").InvokeAsync()
|
||||
[System.Threading.Tasks.Task]::WaitAll(@($r1, $r2))
|
||||
$r1.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r1.IsCompletedSuccessfully | Should -Be $true
|
||||
$r2.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r2.IsCompletedSuccessfully | Should -Be $true
|
||||
$results = @($r1.Result.foreach('Value')) + @($r2.Result.foreach('Value'))
|
||||
Compare-Object -ReferenceObject @(1..20) -DifferenceObject $results -SyncWindow 20 | Should -Be $null
|
||||
} finally {
|
||||
$ps1.Dispose()
|
||||
$ps2.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context 'PowerShell::InvokeAsync - With input and output' {
|
||||
BeforeAll {
|
||||
$d1 = New-Object -TypeName 'System.Management.Automation.PSDataCollection[int]'
|
||||
$d2 = New-Object -TypeName 'System.Management.Automation.PSDataCollection[int]'
|
||||
foreach ($i in 1..20) {
|
||||
$d1.Add($foreach.Current)
|
||||
$foreach.MoveNext() > $null
|
||||
$d2.Add($foreach.Current)
|
||||
}
|
||||
$d1.Complete()
|
||||
$d2.Complete()
|
||||
$script = "`$input${sbStub}"
|
||||
}
|
||||
|
||||
It 'can invoke multiple scripts asynchronously with input' {
|
||||
$ps1 = [powershell]::Create()
|
||||
$ps2 = [powershell]::Create()
|
||||
try {
|
||||
$r1 = $ps1.AddScript($script).InvokeAsync($d1)
|
||||
$r2 = $ps2.AddScript($script).InvokeAsync($d2)
|
||||
[System.Threading.Tasks.Task]::WaitAll(@($r1, $r2))
|
||||
$r1.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r1.IsCompletedSuccessfully | Should -Be $true
|
||||
$r2.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r2.IsCompletedSuccessfully | Should -Be $true
|
||||
$allResults = @($r1.Result) + @($r2.Result)
|
||||
Compare-Object -ReferenceObject @(1..20) -DifferenceObject $allResults.Value -SyncWindow 20 | Should -Be $null
|
||||
} finally {
|
||||
$ps1.Dispose()
|
||||
$ps2.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
It 'can invoke multiple scripts asynchronously with input and capture output' {
|
||||
$ps1 = [powershell]::Create()
|
||||
$ps2 = [powershell]::Create()
|
||||
try {
|
||||
$o = New-Object -TypeName 'System.Management.Automation.PSDataCollection[PSObject]'
|
||||
$r1 = $ps1.AddScript($script).InvokeAsync($d1, $o)
|
||||
$r2 = $ps2.AddScript($script).InvokeAsync($d2, $o)
|
||||
[System.Threading.Tasks.Task]::WaitAll(@($r1, $r2))
|
||||
$o.Complete()
|
||||
$r1.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r1.IsCompletedSuccessfully | Should -Be $true
|
||||
$r2.Status | Should -Be ([System.Threading.Tasks.TaskStatus]::RanToCompletion)
|
||||
$r2.IsCompletedSuccessfully | Should -Be $true
|
||||
Compare-Object -ReferenceObject @(1..20) -DifferenceObject $o.Value -SyncWindow 20 | Should -Be $null
|
||||
} finally {
|
||||
$ps1.Dispose()
|
||||
$ps2.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context 'PowerShell::StopAsync' {
|
||||
It 'can stop a script that is running asynchronously' {
|
||||
$ps = [powershell]::Create()
|
||||
try {
|
||||
$ir = $ps.AddScript("Start-Sleep -Seconds 60").InvokeAsync()
|
||||
Wait-UntilTrue { $ps.InvocationStateInfo.State -eq [System.Management.Automation.PSInvocationState]::Running }
|
||||
$sr = $ps.StopAsync({}, $null)
|
||||
[System.Threading.Tasks.Task]::WaitAll(@($sr))
|
||||
$sr.IsCompletedSuccessfully | Should -Be $true
|
||||
$ir.IsFaulted | Should -Be $true
|
||||
$ir.Exception -is [System.AggregateException] | Should -Be $true
|
||||
$ir.Exception.InnerException -is [System.Management.Automation.PipelineStoppedException] | Should -Be $true
|
||||
$ps.InvocationStateInfo.State | Should -Be ([System.Management.Automation.PSInvocationState]::Stopped)
|
||||
} finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user