Yes, I know it is not as good as <insert your favorite shell here>, but on Windows it makes my life easier.
I've been using it for many years now, built my own scripts for easy stuff and CmdLets for more interesting scenarios.
Lately .NET - based coding starts moving into asynchronous territory with a great speed - most of the modern libraries introduced a Task-based asynchronous API. Using these APIs we can write much more efficient code. And with introduction of .NET 4.5 and async keyword in C#, there are no excuses NOT to write asynchronous code anymore.
Unless you work on a PowerShell Cmdlet.
In typical CmdLet you implement at least one method:
In typical CmdLet you implement at least one method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override void ProcessRecord() | |
{ | |
// processing logic | |
} |
For more advanced scenarios, focused on processing pipelined data you may also consider implementing these methods:
From inside these methods you can interact with PowerShell using a family of Write-* methods like Write-Host, Write-Warning, Write-Verbose, etc.
Also you can ask user to confirm operations using ShouldProcess and ShouldContinue methods.
The only problem is that you can use these methods only from the main thread!
PowerShell validates the stack of the method call and if it is not initiated from one of the 4 primary CmdLet methods, it will throw an exception.
It makes writing asynchronous code using modern libraries a difficult task.
There are many ways to solve this problem, but my goal was to make writing async PowerShell code as easy as a normal one, and have a way to upgrade an existing CmdLet with a minimum effort.
Introducing PowerShellAsync library
First of all, to build a new async-enabled CmdLet or upgrade an existing one, you need to install PowerShellAsync from nuget:
PM> Install-Package TTRider.PowerShellAsync.dll
(https://www.nuget.org/packages/TTRider.PowerShellAsync.dll/)
And if you curious about implementation, feel free to take a look at the source: https://github.com/ttrider/PowerShellAsync
So now, instead of implementing the original methods, you have to implements their async counterparts! And all of the Write-* methods will work as if they been called from a main thread!
So, if you want your CmdLet to execute T-SQL statement on multiple databases at the same time using modern async API, here is your naive PowerShell CmdLet:
For the complete example, please take a look at Github: https://github.com/ttrider/PowerShellAsync/tree/master/PowerShellAsyncExample
Happy Coding !
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected override void BeginProcessing() | |
{ | |
// before processing of the first item in a pipeline | |
} | |
protected override void ProcessRecord() | |
{ | |
// process item from a pipeline | |
} | |
protected override void EndProcessing() | |
{ | |
// after processing of the last item in a pipeline | |
} | |
protected override void StopProcessing() | |
{ | |
// do cleanup when processing has been aborted | |
} |
Also you can ask user to confirm operations using ShouldProcess and ShouldContinue methods.
The only problem is that you can use these methods only from the main thread!
PowerShell validates the stack of the method call and if it is not initiated from one of the 4 primary CmdLet methods, it will throw an exception.
It makes writing asynchronous code using modern libraries a difficult task.
There are many ways to solve this problem, but my goal was to make writing async PowerShell code as easy as a normal one, and have a way to upgrade an existing CmdLet with a minimum effort.
Introducing PowerShellAsync library
First of all, to build a new async-enabled CmdLet or upgrade an existing one, you need to install PowerShellAsync from nuget:
PM> Install-Package TTRider.PowerShellAsync.dll
(https://www.nuget.org/packages/TTRider.PowerShellAsync.dll/)
And if you curious about implementation, feel free to take a look at the source: https://github.com/ttrider/PowerShellAsync
So now, instead of implementing the original methods, you have to implements their async counterparts! And all of the Write-* methods will work as if they been called from a main thread!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected async override Task BeginProcessingAsync() | |
{ | |
// before processing of the first item in a pipeline | |
} | |
protected async override Task ProcessRecordAsync() | |
{ | |
// process item from a pipeline | |
} | |
protected async override Task EndProcessingAsync() | |
{ | |
// after processing of the last item in a pipeline | |
} | |
protected async override Task StopProcessingAsync() | |
{ | |
// do cleanup when processing has been aborted | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Data.SqlClient; | |
using System.Linq; | |
using System.Management.Automation; | |
using System.Threading.Tasks; | |
using JetBrains.Annotations; | |
using TTRider.PowerShellAsync; | |
namespace PowerShellAsyncExample | |
{ | |
[Cmdlet(VerbsLifecycle.Invoke, "MultiSql")] | |
public class MultiSqlCmdlet : AsyncCmdlet | |
{ | |
[NotNull, Parameter(Mandatory = true)] | |
public string[] Server { get; set; } | |
protected override Task ProcessRecordAsync() | |
{ | |
return Task.WhenAll( | |
this.Server.Select( | |
server => | |
this.ExecuteStatement(server, "select * from sys.objects"))); | |
} | |
[NotNull] | |
async Task ExecuteStatement([NotNull] string server, [NotNull] string statement) | |
{ | |
var connectionBuilding = new SqlConnectionStringBuilder | |
{ | |
DataSource = server, | |
IntegratedSecurity = true, | |
AsynchronousProcessing = true | |
}; | |
var connection = new SqlConnection(connectionBuilding.ConnectionString); | |
await connection.OpenAsync(); | |
var cmd = connection.CreateCommand(); | |
cmd.CommandText = statement; | |
using (var reader = await cmd.ExecuteReaderAsync()) | |
{ | |
if (await reader.ReadAsync()) | |
{ | |
var names = new string[reader.FieldCount]; | |
for (var i = 0; i < reader.FieldCount; i++) | |
{ | |
names[i] = reader.GetName(i); | |
} | |
do | |
{ | |
var item = new PSObject(); | |
for (var i = 0; i < reader.FieldCount; i++) | |
{ | |
var value = await reader.IsDBNullAsync(i) ? null : reader.GetValue(i); | |
item.Properties.Add(new PSNoteProperty(names[i], value)); | |
} | |
this.WriteObject(item); | |
} while (await reader.ReadAsync()); | |
} | |
} | |
} | |
} | |
} |
Happy Coding !