Tuesday, March 31, 2009

My First PowerShell Script

For a while now, I've been planning on getting my hands dirty using  PowerShell. There are at least four features that make this a pretty compelling scripting environment:

  • The ability to write scripts combining regular scripting commands  with the full power of the .net base class library (also including Cmdlets that you implement youself).
  • The ability to easily define functions as a part of the script.
  • The Cmdlet Verb-Noun naming convention, giving the scripting syntax a consistent and easy to discover feel that is completely missing in the jungle of cryptic abbreviated commands of the scripting environments of yore.
  • Everything is object based, so that when you, for instance, loop through all files in a directory by using the built in Get-ChildItem function, you are in fact accessing objects that represent the files and not just a textual path.

I hereby announce that I have completed my first PowerShell script (see below).

The good thing about being a latecomer is that you get to use the newest version, so I went directly for PowerShell 2.0 CTP3. A very nice thing about this version is that it comes complete with its own Integrated Scripting Environment, and this has been tremendously helpful in the process of understanding the basics and weeding out bugs.

So, what does my very first PowerShell script do? It recursively copies the contents of one directory to another one. As input to this process it takes a list of directory matching filters and a similar list of file extensions to be used as a filter. All in all this serves the purpose of copying the entire contents of a directory will keeping away from certain paths and files as dictated by the filters.

So by giving in this: "_ReSharper","\obj","\bin", "\.svn" as directory filter and this: ".user", ".suo", ".resharper" as file extension filter, I get functionality for copying .NET source code directories without also copying all the crud that is lying around as a by-product of the VS build process, SubVersion, ReSharper and so on.

I guess that everyone with some PowerShell experience will view this script as childishly amateurish, but at least it works .

Your welcome!


function Passes-Filter($dir, $filters) {
foreach($filter in $filters){
if($dir.Contains($filter)) {
return ''
}
}
return 'True'
}

## Checks to see whether the extension of the
## file matches any of the filters. If so
## returns false, else true
function Passes-FileFilter($file, $filters){
$ext = [System.IO.Path]::GetExtension($file).ToLower()
foreach($filter in $filters){
if ($filter.Equals($ext)){
return ''
}
}
return 'True'
}

function Get-DestinationPath($basedir, $candidate, $destdir){
$baseLength = [int]$basedir.Length
$candidateLength = [int]$candidate.Length
if ($candidateLength.Equals($baseLength)){
return ''
}
else {
#Write-Host -ForegroundColor GREEN $candidate
$rightSide = $candidate.Substring($baseLength, ($candidateLength - $baseLength))
$dir = $destdir + $rightSide
return $dir
}
}

function Copy-CodeFile($basedir, $candidate, $destdir) {
$newFile = Get-DestinationPath $basedir $candidate.FullName $destdir
copy $candidate.FullName $newFile
}

function Make-CodeDirectory($basedir, $candidate, $destdir){
$newDir = Get-DestinationPath $basedir $candidate $destdir
if ([System.String]::IsNullOrEmpty($newDir)){
}
else {
mkdir $newDir
}
}

function Traverse-Directory($basedir, $destdir, $dir, $dirfilters, $filefilters) {
# #Write-Host 'About to traverse dir: ' $dir
foreach($candidate in Get-ChildItem $dir) {
if ([System.IO.File]::Exists($candidate.FullName)){
# It's a file
if(Passes-FileFilter $candidate $filefilters) {
Copy-CodeFile $basedir $candidate $destdir
}
}
else {
# It's a directory
if (Passes-Filter $candidate.FullName $dirfilters){
Write-Host -ForegroundColor GREEN $candidate
Make-CodeDirectory $basedir $candidate.FullName $destdir
Traverse-Directory $basedir $destdir $candidate.FullName $dirfilters $filefilters
}
else {
Write-Host -ForegroundColor RED "Stopped in dir filter: " $candidate.FullName
}
}
}
}

## Script entry point
Clear-Host
$dirfilters ="_ReSharper","\obj","\bin", "\.svn"
$filefilters = ".user", ".suo", ".resharper"
$sourceDir = 'C:\depot\MyProject\trunk'
$destDir = 'C:\temp\CopyOfMyProject'
Traverse-Directory $sourceDir $destDir $sourceDir $dirfilters $filefilters

Monday, March 9, 2009

Smart Clients and System.Transactions Part 5 – Fixing the Timeout Problem

This is the fifth installment of an ongoing series about using System.Transactions as the fundament for client-side infrastructure for managing changes among loosely coupled components.

Previous posts: Introduction, Timeout, Enlistments, The transaction sink


Earlier I discussed the time-out problem which was a serious setback to the plan of building a client side change gathering infrastructure based on System.Transactions. How did we fix this problem? Well, we didn't. Instead we had to resort to cheating:

var scopeFactory = IoC.GetInstance<ITransactionScopeFactory>();
using (var scope = scopeFactory.Start())
{
// Transactional code

scope.Complete();
}


 



The above code shows how we now start client side transactions. As you can see, we no longer start a System.Transactions.TransactionScope, but rather look up a factory class (ITransactionScopeFactory) through a service locator, and ask this instance to start a transaction scope. This scope implements the interface ITransactionScope which is defined as  follows:



/// <summary>
/// A scope mimicking the API of System.Transactions.TransactionScope.
/// Defines a single method Complete() used for marking the scope
/// as "successful". This interface extends IDisposable, and when
/// Dispose() is invoked, this scope will instruct the ambient
/// transaction to commit or rollback depending on whether the
/// scope has been completed or not.
/// </summary>
public interface ITransactionScope : IDisposable
{
/// <summary>
/// Marks the scope as complete, resulting in this scope
/// instructing the ambient transaction to commit when
/// Dispose() is invoked later on. If Complete() is never
/// invoked, the scope will force the ambient transaction
/// to rollback upon Dispose().
/// </summary>
void Complete();
}


 



The responsibility of the factory is to determine the type of scope to generate, and this it does by querying the ambient transaction as to whether or not a transaction already has been started. If such a transaction does not exist an instance of ClientTransactionScope is created, and if a transaction already exists, an instance of NestedClientTransactionScope is created. The difference between these two classes lie mainly in their respective constructors and in the Dispose() method:



Constructor and Dispose() of ClientTransactionScope



/// <summary>
/// Initializes a new instance of the <see cref="ClientTransactionScope"/> class.
/// The ambient transaction is automatically started as this instance constructs.
/// </summary>
public ClientTransactionScope()
{
GetClientTransaction().Begin();
}

/// <summary>
/// If Complete() has been invoked prior to this, the
/// ambient transaction will be instructed to commit
/// here, else the transaction will be rolled back
/// </summary>
public virtual void Dispose()
{
if (Completed)
{
GetClientTransaction().Commit();
}
else
{
GetClientTransaction().Rollback();
}
}


 



Constructor and Dispose() of NestedClientTransactionScope



/// <summary>
/// Initializes a new instance of the <see cref="NestedClientTransactionScope"/> class.
/// This constructor does nothing since an ambient transaction already has been
/// started if an instance
/// </summary>
public NestedClientTransactionScope()
{}

/// <summary>
/// If Complete() has been invoked prior to this, nothing happens
/// here. If Complete() has not been invoked, the ambient transaction
/// will be marked as "non commitable". This has ne immediate
/// consequence, but the transaction is doomed and it will be
/// rollback when the outermost scope is disposed regardless of
/// if this scope attempts to rollback or commit the tx.
/// </summary>
public override void Dispose()
{
if (!Completed)
{
GetClientTransaction().MarkInnerTransactionNotCompleted();
}
}


 



The comments in the code explain the distinction between these two classes.



Commit and Rollback



The actual task of finishing the transaction, either  by commit or rollback, is the responsibility of the ambient transaction.  Throughout the lifetime of the scope, enlistments that have detected changes have enlisted with the ambient transaction. The exact details of how this enlistment procedure is done is kept hidden from the enlistments, but what actually happens is that the ambient transaction maintains a dictionary in which the enlistments are added.



When the time to commit or roll back has finally arrived, a real System.Transactions.TransactionScope is started, the registered enlistments are enlisted with the transaction, and Complete() is either invoked or not on the scope depending on whether or not the transaction is meant to be committed or rolled back:



/// <summary>
/// Instructs the transaction to begin the two-phased commit procedure.
/// This will be done except if any nested inner transaction scope
/// have instructed the transaction to rollback prior to this. If this
/// is the case the transaction will roll back the transaction at this
/// point in time.
/// </summary>
public void Commit()
{
if (!canCommit)
{
Rollback();
throw new TransactionAbortedException("Inner scope not completed");
}
using (var scope = new TransactionScope())
{
EnlistAll();
scope.Complete();
}
}

/// <summary>
/// Instructs the transaction to rollback. This will happen at
/// once if the sending scope is the outer scope (fromInnerScope == true)
/// else the rollback will be postponed until when the outer scope
/// requests a commit
/// </summary>
public void Rollback()
{
using (new TransactionScope())
{
EnlistAll();
// Don't Complete the scope,
// resulting in a rollback
}
}

private void EnlistAll()
{
var tx = Transaction.Current;
tx.EnlistVolatile(this, EnlistmentOptions.None);
tx.EnlistVolatile(sink, EnlistmentOptions.None);
foreach (var notification in enlistments.Values)
{
tx.EnlistVolatile(notification, EnlistmentOptions.None);
}
}


 



Conclusion



This concludes this series which has been an attempt at showing the benefits and problems that we have seen when realizing a novel idea: using the functionality of System.Transactions as a "change gathering infrastructure". The idea has proved viable, however the "timeout problem" proved to be a serious bump in the road, and forced us to implement code so that the actual functionality of System.Transactions only comes into play in the final moments of the logical scope.