Skip to content. | Skip to navigation

Personal tools

Navigation

You are here: Home / weblog

Dominic Cronin's weblog

Using powershell to do useful things with XML lists from Tridion

Posted by Dominic Cronin at Dec 30, 2010 08:55 PM |
Filed under: , ,

For a while now I've been trying to persuade anyone that would listen that Windows Powershell is great for interacting with the Tridion object model (TOM). What I mean by this is that you can easily use the powershell to instantiate and use COM objects, and more specifically, TOM objects. In this post, I'm going to take it a bit further, and show how you can use the powershell's XML processing facilities to easily process the lists that are available from the TOM as XML Documents. The example I'm going to use is a script to forcibly finish all the workflow process instances in a Tridion CMS. (This is quite useful if you are doing workflow development, as you can't upload a new version of a workflow while there are process instances that still need to be finished.)

Although useful, the example itself isn't so important. I'm simply using it to demonstrate how to process lists. Tridion offers several API calls that will return a list, and in general, the XML looks very similar. I'm going to aim to finish all my process instances as a "one-liner", although I'm immediately going to cheat by setting up the necessary utility objects as shell variables:

> $tdse = new-object -com TDS.TDSE
> $wfe = $tdse.GetWFE()

As you can see, I'm using the new-object cmdlet to get a TDSE object, specifying that it is a COM object (by default new-object assumes you want a .NET object). Then I'm using $tdse to get the WFE object which offers methods that list workflow items. With these two variables in place, I can attempt my one liner. Here goes:

> ([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances.Item | % {$tdse.GetObject($_.ID,2)} | % {$_.FinishProcess()}

Well, suffice it to say that this works, and once you've run it (assuming you are an admin), you won't have any process instances, but perhaps we need to break it down a bit....

If you start off with just $wfe.GetListProcessInstances(), the powershell will invoke the method for you, and return the XML as a string, which is what GetListProcessInstances returns. Just like this:

> $wfe.GetListProcessInstances()
<?xml version="1.0"?>
<tcm:ListWFProcessInstances xmlns:tcm="http://www.tridion.com/ContentManager/5.0" xmlns:xlink="http://www.w3.org/1999/x
link"><tcm:Item ID="tcm:24-269-131076" PublicationTitle="300 Global Content (NL)" TCMItem="tcm:24-363" Title="Test 1" T
CMItemType="16" ProcessDefinitionTitle="Application Content Approval" ApprovalStatus="Unapproved" ActivityDefinitionTyp
e="1" WorkItem="tcm:24-537-131200" CreationDate="2010-12-30T19:35:33" State="Started" Icon="T16L1P0" Allow="41955328" D
eny="16777216"/><tcm:Item ID="tcm:24-270-131076" PublicationTitle="300 Global Content (NL)" TCMItem="tcm:24-570" Title=
"Test 2" TCMItemType="16" ProcessDefinitionTitle="Application Content Approval" ApprovalStatus="Unapproved" ActivityDef
initionType="1" WorkItem="tcm:24-538-131200" CreationDate="2010-12-30T19:36:04" State="Started" Icon="T16L1P0" Allow="4
1955328" Deny="16777216"/></tcm:ListWFProcessInstances>

OK - that's great - if you dig into it, you'll see that there is a containing element called ListWFProcessInstances, and that what it contains are some Item elements. All of this is in the tcm namespace, and each Item has various attributes. Unfortunately, the XML in this form is ugly and not particularly useful. Fortunately, the powershell has some built-in features that help quite a lot with this. The first is that if you use the [xml] cast operator, the string is transformed into a System.Xml.XmlDocument. To test this, just assign the result of the cast to a variable and use the get-member cmdlet to display it's type and methods:

> $xml = [xml]$wfe.GetListProcessInstances()
> $xml | gm

(Of course, you don't type "get-member". "gm" is sufficient - most standard powershell cmdlets have consistent and memorable short aliases.)

I won't show the output here, as it fills the screen, but at the top, the type is listed, and then you see the API of System.Xml.XmlDocument. (Actually you don't need a variable here, but it's nice to have a go and use some of the API methods.)

All this would be pretty useful even if it stopped there, but it gets better. Because the powershell is intended as a scripting environment, the creators have wrapped an extra layer of goodness around XmlDocument. The assumption is that you probably want to extract some values without having to write XPaths, instantiate Node objects and all that other nonsense, so they let you access Elements and Attributes by magicking up collections of properties. Using the example above, I can simply type the names of the Elements and Attributes I want in a "dot-chain". For example:

> ([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances.Item[0].ID
tcm:24-269-131076

Here you can also see that I'm referencing the first Item element in the collection and getting its ID attribute. The tcm ID is returned. All this is great for exploring the data interactively, but be warned, there is a fly in the ointment. Behind the scenes, the powershell uses its own variable called Item to represent the members of the collections it creates. This means that whereas you ought to be able to type

([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances

and get some meaningful output, instead, you'll get an error saying:

format-default : The member "Item" is already present.
    + CategoryInfo          : NotSpecified: (:) [format-default], ExtendedTypeSystemException
    + FullyQualifiedErrorId : AlreadyPresentPSMemberInfoInternalCollectionAdd,Microsoft.PowerShell.Commands.FormatDefaultCommand

This is because Tridion's list XML uses "Item" for the element name, and it conflicts with powershell's own use of the name. It's an ugly bug in powershell, but fortunately it doesn't affect us much. Instead of saying "ListWFProcessInstances", just keep on typing and say "ListWFProcessInstances.Item" and you are back in the land of sanity.

Apart from this small annoyance, the powershell offers superb discoverability, so for example, it will give you tab completion so that you don't even have to know the name of ListWFProcessInstances. If at any point you are in doubt as to what to type next, just stop where you are and pipe the result into get-member - all will be revealed.

OK - back to the main plot. If you're with me this far, you have probably realised that

([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances.Item

will get you a collection of objects representing the Item elements in the XML. As you probably know, an important feature of powershell is that you can pipeline collections of objects, and that there is syntax built in for processing them. The % character is used as shorthand for foreach, and within the foreach block (delimited by braces), the symbol $_ represents the current item in the iteration. For example, we could write:

> ([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances.Item | % {$_.ID}

and get the output:

tcm:24-269-131076
tcm:24-270-131076

I'm sure you can see where this is going. We need to transform the collection of XML attributes: the IDs of the process instances, into a collection of TOM objects, so with a small alteration in the body of the foreach block, we have

% {$tdse.GetObject($_.ID,2)}

and then we can pipe the resulting collection of TOM objects into a foreach block which invokes the FinishProcess() method:

 

% {$_.FinishProcess()}

Of course, if you like really terse one-liners, you could amalgamate the last two pipeline elements so that instead of:

> ([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances.Item | % {$tdse.GetObject($_.ID,2)} | % {$_.FinishProcess()}

we get:

> ([xml]$wfe.GetListProcessInstances()).ListWFProcessInstances.Item | % {$tdse.GetObject($_.ID,2).FinishProcess()} 

but in practice, you develop these one-liners by exploration, and if you want something really terse, you are more likely to write a more long-hand version, put it in your $profile, and give it an alias.

As I said at the top - this is just an example. All the TOM functions that return XML lists can be treated in a similar manner. Generally all that changes is the name of the root element of the XML document, and as I have pointed out, this is easily discoverable.

I hope this approach proves useful to you. If you have any examples of good applications, please let me know in the comments.

A Happy New Year to you all.

Dominic

A new version of the Tridion developers' Powershell profile for SDL Tridion 2011

Posted by Dominic Cronin at Nov 30, 2010 11:30 AM |
Filed under: ,

As I'm up at Tridion HQ for a few days for the 2011 boot camp, here's an updated version of the powershell profile for Tridion developers. Nothing really new, just that a couple of things got their names changed. (With the added bonus that the service names are now consistently named again!!).

 

# http://www.leastprivilege.com/MyMonadCommandPrompt.aspx
function prompt { "PS " + (get-location).Path + "`n> " }
# http://www.interact-sw.co.uk/iangblog/2007/02/09/pshdetectelevation
& {
  $wid=[System.Security.Principal.WindowsIdentity]::GetCurrent()
  $prp=new-object System.Security.Principal.WindowsPrincipal($wid)
  $adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator
  $IsAdmin=$prp.IsInRole($adm)
  if ($IsAdmin)
  {
    (get-host).UI.RawUI.Backgroundcolor="DarkRed"
    clear-host
  }
}
# http://www.leastprivilege.com/AdminTitleBarForPowerShell.aspx
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object System.Security.Principal.WindowsPrincipal($id)
if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))
{
 $Host.UI.RawUI.WindowTitle = "Administrator: " + $Host.UI.RawUI.WindowTitle
}
"rat"
set-alias -name rat -value RestartAllTridion
function RestartAllTridion
{
"### Restart All Tridion ###"
"Stopping All Tridion CM services"
$runningServices = service     TCMBCCOM, `
                TCMIMPEXP, `
                TcmPublisher, `
                TCMSearchHost, `
                TcmSearchIndexer, `
                TcmServiceHost, `
                TCMWorkflow `
        | where {$_.Status -eq "Running"}
$runningServices | % {stop-service -force -InputObject $_}
kt
"Doing an IISRESET"
iisreset
"Starting Tridion services"
# This script basically does best-effort, so we need a sick-bag in case a service is disabled or whatever 
# (feel free to wire up the WMI stuff if you need to scratch this)
&{
  trap [Exception] {}
  $runningServices | where { "Stopped", "StopPending" -contains $_.Status } | start-service
  }
}
"kt"
set-alias -name kt -value killTridion
function KillTridion {
"Shutting down Tridion COM+ Application"
$COMAdminCatalog = new-object -com COMAdmin.COMAdminCatalog
$COMAdminCatalog.ShutdownApplication("SDL Tridion Content Manager")
}

Javascript for Tridion scripting

Posted by Dominic Cronin at Oct 09, 2010 05:30 PM |
Filed under: , ,

During the recent Tridion MVP summit, the subject of Javascript came up. That's not surprising, as we were busy with building some GUI extensions to run on the SDL Tridion 2011 CTP, which is a pretty Javascript intensive activity. Most Tridion people reading this won't be too surprised if I say that Javascript isn't really in the comfort zone for many of us. We've done templating in VBScript, and although Tridion has always supported Javascript (or more strictly, JScript) for templating, we've always avoided it. Tridion has never shipped a JavaScript version of the default templates, and I suspect that's why, very early on, VBScript became the VHS to JavaScript's BetaMax. Most people accept that Betamax lost to VHS despite being technologically better, and for the sake of this discussion I'm going to ignore the dissenting opinions that have surfaced recently. In my view JavaScript is a superior technology to VBScript in many ways, but VBScript achieved critical mass in our world, and that's that. Or is it?

Well to start with, I'm not about to suggest that anyone should start to write Tridion component templates in JavaScript. These days, if you're defecting from VBScript templating, you'll be going to a .NET language; probably C#, and I'm all in favour of that. On the other hand, for many of us, JavaScript clearly has a place in our future. If you're writing web applications these days, a solid grasp of js is essential, and as noted already, if you want to customise the Tridion GUI, you'll be elbow-deep in the stuff before you get anything useful done.

For myself, I started using Javascript for simple update scripts and the like quite some time ago, and this mostly means running the scripts from the command line on the server using the cscript processor built-in to Windows. (These days, I also use PowerShell scripting for some of the more ad hoc work, but that's another story.)

The first obvious point about the language is that it has several styles or idioms in common use. For those of you that are familiar with libraries such as JQuery, you'll know that the style can be very similar to what you might encounter in languages with a functional flavour. To gain expertise in this style, I'd strongly recommend jumping in the deep end with John Resig's "Learning Advanced Javascript". (It's a pretty deep deep end. Note to self: have another go soon, and try to understand it this time!)

The other two idioms I'd mention are the object-oriented style, and what I can only describe as Microsoft style. For the object-oriented style, and how to achieve it, you can do no better than to view Doug Crockford's lecture series hosted at Yahoo. I suppose the best place to learn the Microsoft style is MSDN. In the days of the Atlas project, Microsoft tried to use JavaScript as just another layer in the ASP.NET stack, and with some success. For Tridion guys, this style is most notable because some of this flavour is to be seen in the Tridion GUI - or at least that's how it appears to me based on a fairly peremptory poke around. (I'd love someone to tell me if if I'm wrong. Really! Although comments won't show up, as I have to moderate to prevent spam. That's my only moderation criterion, though. If you take the trouble to write a real comment, I'll publish it.)

I hope to return to to the subject of Tridion and Javascript in future posts, but for now, I'd just like to start with a simple example of why it works for me. But please be kind; I'm not a Resig or a Crockford.

It's fairly often useful to be able to recurse through a folder structure and process each item within a given hierarchy. Usually, the processing involves two parts. Firstly, filtering: am I interested in this item or not? Secondly, for the items I'm interested in, I want to run some code, either to report on the item or to alter it in some way. This pattern comes up a lot, and for a lot of the small- to medium-scale jobs, the recurse-filter-process logic is a significant part of the work. Maybe the actual payload only amounts to a couple of lines of code. If you had a simple way to re-use the recurse-filter-process part, you could do a lot of quick-and-dirty jobs, erm... pretty quickly.

In Javascript, I can paste the following function in to a template or a script file, and the recurse-filter-process part is done:

function recurseItems( orgItem, filter, process){
  var items = orgItem.GetItems();
  var eItems = new Enumerator(items);
  for (;!eItems.atEnd();eItems.moveNext()){
    var itemType = eItems.item().ItemType;
    if (itemType === TDSDefines.ItemType.Folder || itemType === TDSDefines.ItemType.StructureGroup){
        recurseItems(eItems.item(), filter, process)
    } else {
      if (filter(eItems.item())) {
          process(eItems.item());
      }
    }
  }
}

I'm quite sure you could tidy this up a bit, but whatever... What makes this so straightforward to re-use is the fact that in JavaScript, functions are first-class objects, and it's very easy to pass functions as arguments to another function, and invoke them from within that function. The recurseItems() function expects to be passed an organizational Item as its first argument. (OK - as written, this won't work for categories, or for the sake of argument, publications, but bear with me...)

The "filter", and "process" arguments are functions.

Let's say I want to list all the components in a given hierarchy. I could write something like this (by which I mean this code isn't tested, it's for illustration purposes, right?):

function isComponent(item){return item.ItemType === TDSDefines.ItemType.Component;}
function outputTitle(item){WScript.Echo(item.Title + "\n";))
var topFolder = tdse.GetObject("tcm:1-1234-2", TDSDefines.OpenMode.View);
recurseItems(topFolder, isComponent, outputTitle);

So with 4 lines of code, I've listed the items I'm interested in. Not bad, eh? OK - I cheated. But how? Take a look at the attached file TDSDefines.js. It's a port of the standard TDSDefines constants to JavaScript. The cheating part is that I instantiate the "tdse" object in there, which somewhat breaks the purity of having a TDSDefines file, but you're always going to want tdse, so wtf not? Anyway - this file is what allows me to type things like "TDSDefines.ItemType.Component", or TDSDefines.OpenMode.View, instead of 16, or 1. JavaScript lends itself very well to this kind of nested data structure, in ways that VBScript would struggle with.

Assuming you are using the cscript host on your Tridion server, and that TDSDefines.js is in the same directory as your script, you'll need to use a couple of lines of code to import your "defines".

    var fso = new ActiveXObject("Scripting.FileSystemObject");
    eval(fso.OpenTextFile(fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), "TDSDefines.js"), 1).ReadAll());

Inside Tridion, of course, TDSDefines.js just becomes a template building block, and gets included in the normal way.

Funnily enough, I never got round to porting the default template code to JavaScript, and now, presumably, I never will. All the same, using JavaScript for this kind of work has allowed me to practice and get more familiar with the language, and to have a toolkit that allows for very, very fast creation of quick-and-dirty recursion scripts among others. It doesn't end there. JavaScript, or perhaps JSON, allows you to take a script-as-data approach which will get you to your desired result much quicker than, for example, having to write scripts that crunch through XML data files. Perhaps that would make a good subject for a future post.

In the meantime, I hope this gives you yet another excuse to hone your JavaScript skills. Those skills will definitely come in useful.

Engineers and bloody fools

Posted by Dominic Cronin at Jul 22, 2010 09:35 PM |
Filed under:

"An engineer is a man who can do for five bob what any bloody fool can do for a quid."

I was scratching around today  for the origin of this quote, or even how the exact quote ought to go. A bit of googling this evening solved it. It was Neville Shute - apparently! At least the quote comes from one of his books, and is probably something he might have said.

http://www.nevilshute.org/Biography/alanbesterbio.php

Just in case anyone's struggling with the "old money" - there were twenty bob in a quid. If anyone is struggling with the social conventions of the day, well I don't suppose women were included in the bloody fools either, but in any case, take it to your local chapter... nothing to see here.

Extending the boot partition of Windows 2003 Server

Posted by Dominic Cronin at Apr 04, 2010 06:25 PM |
Filed under: ,

In my work I quite commonly have servers on which over time I end up wanting to install more software, or simply upgrading what's there already, but in any case, consuming more disk space than originally envisaged by the person who set up the server. Today I was upgrading a Tridion development image to Tridion 2009 SP1. This was on a VMWare image whose C: drive was maxed out at 16GB. A while ago, I'd filled up the 16GB, and then gone though the rigmarole of trying to make it larger, and failed. At the time, it was more expedient to just add another disk and move some of the data off the C: drive. This approach only gets you so far, and sooner or later, you need a bigger C: drive.

So I'd already got as far as using the VMWare utilities to increase the size of the "physical" disk to 20GB, and then I'd booted the image from a GParted "live CD" .iso. and increased the size of the partition, also to 20GB. The problem was that although in the disk management snap-in you could see the full 20 GB, as far as Windows Explorer was concerned, there were only 16GB (and a pretty full 16GB at that!)

If you've ever been through this, you'll know that it can be about getting the magical incantations just right. The operating systems' own tools won't let you expand the boot partition, or a system partition, or the partition where the page file is or a whole bunch of other strange restrictions. (Yes - strange - even in 2003!) The reason for using gparted in the first place was that you definitely can't do it while Windows is running off the offending partition, and at some time in the past, I'd followed this approach, and it had just worked. Why not now? I don't know.

To cut a long story short, it just sat there staring at me, and wouldn't do what I wanted, when for no really good reason, I booted the system from gparted again, and this time used the "check and repair' utility. It duly checked and repaired, and I rebooted the system normally again, only to see that Windows had now decided that the disk was suspect and wanted to run CHKDSK. I let it run, and hey-presto, when the system came up, Windows Explorer could see the full 20GB. Job's a good'un, eh?

So while I don't have any solid explanation for it all, but in the hope that it helps someone - perhaps myself on some future occasion, I'm adding a blog entry. I don't know whether it was actually something that the gparted check/repair did that fixed the problem. In terms of probabilities, I'm leaning more in the direction that it was CHKDSK that did the actual fixing. If anyone is in the same boat, I'd be interested to know whether just running CHKDSK is sufficient.

How to treat and avoid burns

Posted by Dominic Cronin at Mar 31, 2010 10:05 PM |

I went to a talk this evening given by a lady from the Dutch foundation for burns. She showed a couple of shocking videos, but for me, as a parent of small children, what really caught my attention was the fact that 10 minutes after being poured out, half a cup of tea is sufficient to cause life-threatening burns to a toddler. Be careful out there!

Tweeting from powershell

Posted by Dominic Cronin at Mar 30, 2010 09:45 PM |
Filed under:

This evening, instead of hanging out at the Microsoft Dev Days Geek Night, I drove home, put the kids to bed, and sat down to figure out how to update my Twitter status from the Windows Powershell. This was inspired by the bash one-liner using curl that I learned about from Peteris Krumins’ blog (recommended). Well, it turns out not to be a one-liner in powershell, but FWIW - here's how!

 

function tweet([string] $status) {
   #http://blogs.msdn.com/shitals/archive/2008/12/27/9254245.aspx
  [System.Net.ServicePointManager]::Expect100Continue = $false
  try {
    $wc = new-object System.Net.WebClient
    $wc.BaseAddress = "http://twitter.com"
    $wc.Credentials = new-object System.Net.NetworkCredential
    $wc.Credentials.UserName = "Your account name"
    $wc.Credentials.Password = "password"
    $stream = $wc.OpenWrite("statuses/update.xml")
    $writer = new-object System.IO.StreamWriter -ArgumentList $stream
    $writer.Write("status=" + $status) 
  }
  finally {
    $writer.Dispose()
    $stream.Dispose()
    $wc.Dispose()
  }
}

A noteable namesake

Posted by Dominic Cronin at Mar 06, 2010 06:35 PM |

Apparently, I have a noteable namesake; there was a Dominic Cronin who played cricket for Ireland. Who knew?

http://www.cricketeurope4.net/CSTATZ/irelandall/bio/c/cronin_d.html

More on code re-use and libraries.

Posted by Dominic Cronin at Feb 28, 2010 06:30 PM |

Back in June, I wrote a blog post in reaction to Udi Dahan's "The fallacy of ReUse". Now Mark Needham has published a blog post with a very similar theme. One interesting insight is that sometimes sharing domain objects can be dangerous, because as he puts it "although we often do have the same domain concepts in different projects, it's quite rare that they mean exactly the same thing".

Definitely worth a read.

SDL Tridion MVP award

Posted by Dominic Cronin at Feb 24, 2010 09:30 PM |

Today the official announcement has been made of the awards in SDL's Tridion MVP (Most valued professional) programme.

I'm on the list. I'm now a Tridion MVP. [insert happy-dance here]

I'd like to say I'm surprised and honoured, but as I was on the committee responsible for making the awards, I'll have to settle for simply being honoured. (Of course, I wasn't allowed to vote for myself.) The award is given to those people who have been most visibly involved in community activities in the world of Tridon. This mostly means being willing to share ones knowledge and experience with others, for example on the Tridion technical forum, ideas site or building blocks exchange. Notwithstanding the fact that participating in these activities is a pleasure in itself, I am very pleased to receive the award, and to be counted in the same company as the other MVPs.

I really hope that now as a group we can go on to do even more, and that the whole will prove to be greater than the sum of the parts.

So to my fellow committee members, and to the Tridion community at large; thank you.