Dominic Cronin's weblog
What should we hope for Tridion workflow?
Over at Tridion Developer, Chris Summers posts today about the forthcoming Tridion Bundles feature, and how it may be bringing a revival to the fortunes of Tridion workflow. He says: "Now given the sparkle I have seen in people’s eyes whenever they say 'Bundles…', I am pretty confident that something great is coming in the next major release, and I thought it might be time resurrect the workflow debate in the community – if we get vocal about these things now, we may see some of our dreams sneaking in with the bundles."
Picking up on this thought, I've set out to attempt to articulate what my concerns with the existing implementation have been and what I hope for in any new approach. I don't know very much about the Bundles feature - it's all pretty much still under wraps over at Tridion HQ, so I'm not really going to cast my criticisms and suggestions in terms of Bundles. Maybe the Bundles team have already considered all these concerns, and their design covers the same ground in an entirely different way. I'm just going to put it in terms of how things are now, and you can extrapolate.
So what's wrong with workflow as it now stands? Well you'll get different answers depending on who you talk to. If you ask different people what Workflow features in a product like Tridion should offer, you'll generally get answers that fall roughly in to one of these categories:
- Something intended to enforce governance requirements, where some level of process automation is a necessary evil
- Something which is intended primarily to support process automation, and within which enforcing governance rules is one of several possible applications
On more than one occasion, I've heard criticisms of Tridion from people whose prior experience had led them to expect the latter. My defence of Tridion would usually come out of the notion that Tridion's workflow support is in the first category, and was never intended to be the second. (OK - if pressed, I have to accept that the literal meaning of the word Workflow is far closer to the second than the first) Now right there, you've already got a huge debate. If Chris asks: "Why can’t Tridion notify me when I have something to review", then perhaps he's suggesting that some process automation might be a good thing. Whatever... Chris doesn't need me to put words in his mouth, and I'm sure he'll make his position clear in his promised expansions on the subject. For myself, I don't have a real problem if Tridion doesn't support process automation "out of the box", as long as it's not too hard to wire it up if that's what you want. If SDL chose to offer workflow support as a third-party integration, I'd be fine with that, although I suspect that the amount of architectural changes required might be significant. As a web content management system intended for enterprise customers, there will always be a proportion of those who need to implement governance rules, and for them, I suspect the built-in support would need to do at least this much, as the current implementation does.
In terms of the existing approach, the biggest problem is probably that workflow controls access to versions by riding on the back of the check-in mechanism. The check-in mechanism in turn is designed to ensure that when you are working on something, other people can't see the change until you check it in. When an item "goes in to" workflow, the item is checked-out to the system workflow user, and remains checked out until it leaves workflow. Intermediate saves don't get a new version number, but a "revision", and these revisions are visible to the current workflow assignee if they use the workflow features to access the item. At first sight, this seems like a great plan, because the workflow sub-system doesn't have to do a thing to manage the visibility of the version of the item. If only it were so simple.
Well almost: there's a second mechanism - approval statuses - whereby workflow controls which version of an item can be used for publishing. If this worked as you might intuitively imagine it worked, then it would be great. By using workflow, you can assign an approval status to an item. If you see that approval status, you know that the item has been through a specific workflow activity. Approval statuses are in an ordered list, and you can specify a Minimum Approval Status on a publication target. The fly in the ointment is that Tridion only respects these approval statuses while the item remains in workflow.
This combination of workflow items being checked-out, and an approval status mechanism that doesn't work outside workflow, adds up to some pretty tricky problems to solve if, for example, you want to have workflow on both pages and components. Especially for the end users, it can be particularly disturbing when the edit you have just made to a component disappears when you view the same thing from the page.
The basic idea behind approval statuses is a good one. Specifically, the idea that you can associate data with a given version of an item, and that this allows you to evaluate which version of an item ought to be used for a particular activity, or even more generally to drive any business rule. The key thing here is that the only way you can write that data is to ensure that the item goes through a specific workflow activity. (Apart from that last condition, doesn't this remind you of Application Data?) If this "workflow data" were to include some kind of visibility constraints, then maybe you could do away with the dependency on the check-out mechanism. If nothing else, removing that dependency would probably make it much more feasible to integrate with third-party tools.
OK - as I've said, I'm suggesting a radical re-architecture, and I'm quite sure that such things take far more consideration than it has taken me to knock out a quick blog article, As I understand it, the Bundles thinking is already fairly far developed, and as I've said, maybe they've taken a completely different view of things. Still - I think Chris is right to suggest that now is the time for us to be vocal about what we want. What if the workflow experience were smooth enough that we could consider using it for process automation? As things stand, we mostly end up advising people that workflow is a measure only to be used in cases of necessity. There is so much scope for a positive change here, that I'm really looking forward to the next generation of Tridion workflow, whatever that ends up looking like.
MSSQL TCP Dynamic ports and Tridion Content Delivery
Recently, I was setting up Tridion content delivery on my development server. This runs as a VMWare image on my laptop, while the MSSQL database runs without virtualisation on the same laptop. If you read this earlier post, you will know that I like to have a script to check all my settings in advance (the script uses the standard .NET data access classes). I had done this, and everything was fine, so I was mildly surprised to find that as soon as I tried to publish anything I got error messages about being unable to connect to the database. The relevant part of my storage configuration looked like this:
<Storage Id="brokerdb" Type="persistence" dialect="MSSQL" Class="com.tridion.storage.persistence.JPADAOFactory"> <Pool Type="jdbc" Size="5" MonitorInterval="60" IdleTimeout="120" CheckoutTimeout="120" /> <DataSource Class="com.microsoft.sqlserver.jdbc.SQLServerDataSource"> <Property Name="serverName" Value="WSL117\DEVELOPER" /> <Property Name="portNumber" Value="1433" /> <Property Name="databaseName" Value="Tridion_Broker" /> <Property Name="user" Value="TridionBrokerUser" /> <Property Name="password" Value="topsecret" /> </DataSource> </Storage>
OK - so all those settings were just the same as in my test script, except that the test script didn't specify the port number, but that's the default port, so nothing to see there, eh?
Anyway - the message was clear, it was something to do with the connection, so I went to check that MSSQL was listening on the expected port with a quick "netstat -oan". Lo - and behold, it was nowhere to be seen. Eventually I discovered that in the Sql Server Configuration Manager you can configure the port, and that there there's a setting called "TCP Dynamic Ports", which was switched on.
At this point, I could simply have configured a static port number and moved on, but I was intrigued. If MSSQL wasn't listening on a static port, how did my test script succeed? OK - it didn't seem too unreasonable that whatever mechanism was in play should be understood by the .NET framework, but could I get it to work from a Java-based system. Well after a bit of Googling, it turned out that there's a service called the SQL Server browser, which lets the client know what port it needs to connect to. Not only that, but it seems the Microsoft JDBC driver, which I was using, also supports this mechanism. I commented out the Property element that specifies the port number, restarted IIS (this was an HTTP Upload site) and sure enough, when I tested it again, everything worked great.
All this: the sweet smell of success, and yet somehow I was still troubled. Why on earth would Microsoft have introduced this dynamic mechanism? After all, it just means more configuration. Extra stuff to tweak. Extra stuff to go wrong. So why? It turns out that the answer is pretty straightforward. This quote from the SQL Server Help explains it all:
Prior to SQL Server 2000, only one instance of SQL Server could be installed on a computer. SQL Server listened for incoming requests on port 1433, assigned to SQL Server by the official Internet Assigned Numbers Authority (IANA). Only one instance of SQL Server can use a port, so when SQL Server 2000 introduced support for multiple instances of SQL Server, SQL Server Resolution Protocol (SSRP) was developed to listen on UDP port 1434. This listener service responded to client requests with the names of the installed instances, and the ports or named pipes used by the instance. To resolve limitations of the SSRP system, SQL Server 2005 introduced the SQL Server Browser service as a replacement for SSRP.
I had installed a named instance of MSSQL alongside the existing SQLEXPRESS instance, so perhaps I should have figured this out myself. Whatever - at least it explains why things are set up this way. I chatted with a colleague from Indivirtual's hosting partner Sentia, and he confirmed that for Tridion infrastructure jobs, one of the tasks they have to do is configure MSSQL to listen on static ports. For a dedicated server, of course, this is the obvious choice.
Still - for the kind of configuration I have for development and research, it's great that the dynamic ports feature works well with Tridion. Of course, that's not the same thing as being a supported configuration. As with any enterprise software vendor, SDL Tridion only generally support configurations they have tested. In order to ensure that this gets "on to their radar", I've created an "idea" on ideas.sdltridion.com. (If you have a login there, you can go and vote it up if you like!) Hopefully in future releases, this will become a tested and supported configuration.
Anyway - there was a time when I did far more infrastructure work than I've done lately. I guess it shows!
JRE/JDK 6u29 doesn't work with Tridion content delivery and MSSQL
I've just spent an excessive amount of time trying to get Content Delivery set up on my SDL Tridion 2011 SP1 image. I couldn't get the HttpUpload web page working, no matter how much I tweaked the various settings. Then I gave up on that and tried setting up a deployer service instead. Still no joy; it wouldn't start. Fortunately I was able to call on my friends in the Tridion community, and Nuno Linhares came up with the answer. It turns out that the specific update of Java I had installed somehow doesn't get along with some of the other pieces in the puzzle. According to Nuno, it had something to do with using MSSQL as well, but frankly, I was just pleased to find that when I uninstalled 6u29 and installed 6u30, everything worked fine. Thanks Nuno.
Batching components for the component synchronizer
Although the Tridion power tools are currently undergoing a major overhaul to bring them in line with SDL Tridion 2011 and its Anguilla extensibility framework, many of us will be working on pre-2011 systems for some time to come. This being so, the "old" power tools remain a useful resource. Today I was working on a 2009 system, where we are busy with a data migration. This means using the component synchronizer. We've been using it for a few things, but today we wanted to remove some redundant fields from a schema, and then have that reflected in the data. The schema in question had about 8500 components based on it, and processing the entire list in one go was going to slow the whole team down. (We're doing this on a relatively under-powered image that can run quite slowly sometimes - it's easy to max it out!)
So what to do? The way of using the component synchronizer that I'm most familiar with is simply to select the schema and then process all it's components. However, the synchronizer also has an option to process a specific list of components; you have to paste a comma-separated list of TCM URIs into a text box. So then the question was how to get such lists with a reasonable amount of effort. Powershell to the rescue. Here's the approach:
> $tdse = new-object -com TDS.TDSE
> $alg = $tdse.GetObject("tcm:12-1255-8",1)
> $f = $tdse.CreateListRowFilter()
> $f.SetCondition("ItemType", 16)
> $docAlgItems = [xml]$alg.Info.GetListUsingItems(1,$f)
> $AlgTcms = $docAlgItems.ListUsingItems.Item | %{$_.ID}
> $algTcms[0..999] -join "," | out-file first1000.txt
> notepad .\first1000.txt
As you can see, we start with instantiating TDSE and getting hold of the schema. To protect the innocent, let's pretend that this schema is called Algernon. $alg is the variable representing the schema.
So - after a quick where-used, we do the standard Powershell trick of casting the results to an XmlDocument and reading off the ID attributes. By this time, $AlgTcms contains an array of TCM URIs and it's almost trivial to dump the first thousand into a file by specifying an array range (obviously you can get the second thousand by saying $AlgTcms[1000..1999] and so on) and doing a -join
So instead of maxing out the system for several hours, we were able to schedule the work in reasonable batches through the day.
Smoke-testing my Tridion database connections
I'm installing and configuring Tridion 2011 SP1. There are now so many databases, that it's just insane to try to keep track of them all by hand, but nil desperandum, the power shell is here. OK - you might not be quite so compulsive/obsessive, but I threw together a script that lets me have a list of verified working logins before I start poking at config files. At the very least, it brings out some findings about consistency across the different products. Here's what I did:
function CheckDatabase($connStringBuilder, $queryString="select DB_VERSION from TDS_DB_INFO", $CommandType="Text"){ $conn = new-object System.Data.SqlClient.SqlConnection $conn.ConnectionString = $connStringBuilder.ConnectionString $conn.Open() $comm = new-object System.Data.SqlClient.SqlCommand $comm.CommandText = $queryString $comm.CommandType = $CommandType $comm.Connection = $conn $reader = $comm.ExecuteReader() $readResult = $reader.Read() $dbversion = $reader.GetString(0) $reader.Close() $Conn.Close() $dbName = $connStringBuilder["Initial Catalog"] if ($dbversion.length -gt 0) {"$dbname Database version found: $dbversion"} else {"$dbname fffft"} } $connStringBuilder = new-object System.Data.SqlClient.SqlConnectionStringBuilder $connStringBuilder["Data Source"] = "MY_LAPTOP\DEVELOPER" $connStringBuilder["Initial Catalog"] = "Tridion_cm" $connStringBuilder["User ID"] = "TCMDBUSER" $connStringBuilder["Password"] = "Yes I used the same password for all dbs - don't you?" CheckDatabase $connStringBuilder $connStringBuilder["Initial Catalog"] = "Tridion_Broker" $connStringBuilder["User ID"] = "TridionBrokerUser" CheckDatabase $connStringBuilder $connStringBuilder["Initial Catalog"] = "Tridion_cm_email" $connStringBuilder["User ID"] = "TMSDBUSER" CheckDatabase $connStringBuilder "select DB_VERSION from OE_DB_INFO" $connStringBuilder["Initial Catalog"] = "Tridion_submgmt" $connStringBuilder["User ID"] = "TMSSMUSER" CheckDatabase $connStringBuilder "select DB_VERSION from DB_INFO" $connStringBuilder["Initial Catalog"] = "Tridion_tracking" $connStringBuilder["User ID"] = "TMSPSUSER" CheckDatabase $connStringBuilder "PS_READ_DBINFO" "StoredProcedure" $connStringBuilder["Initial Catalog"] = "Tridion_TranslationManager" $connStringBuilder["User ID"] = "TMUser" CheckDatabase $connStringBuilder "SELECT DB_VERSION FROM TM_DB_INFO" $connStringBuilder["Initial Catalog"] = "Tridion_Ugc" $connStringBuilder["User ID"] = "TridionUgcUser" CheckDatabase $connStringBuilder "SELECT DB_VERSION FROM UGC_TDS_DB_INFO"
And here's what the output looked like:
. C:\Users\Administrator\Desktop\dbTest.ps1 Tridion_cm Database version found: 6.1.0.0 Tridion_Broker Database version found: 6.1.0.0 Tridion_cm_email Database version found: 2.2.0.0 Tridion_submgmt Database version found: 2.2.0.0 Tridion_tracking Database version found: 2.2.0.0 Tridion_TranslationManager Database version found: 3.0.0.0 Tridion_Ugc Database version found: 6.1.0.0
All in all - maybe not worth the effort, but somehow satisfying. Is it useful? Maybe.
Breathing new life into the Tridion power tools
A week or so ago we had the second Tridion MVP retreat. SDL's Tridion MVP programme is intended to encourage and inspire people to build a community among people who use the Tridion range of products. People who contribute visibly to building the community are recognised by the Most Valued Professional award. As one of this year's recipients, I was invited to spend a few days as Tridion's guest at a "retreat" in Portugal, also attended by the Community Builders (which is how Tridion extends the same recognition to it's own people who are active in the community).
It was an intense few days. The MVPs and Community Builders are social, driven and experts in their field; you couldn't wish for a more stimulating group of people to bounce ideas off, and the discussions ranged far and wide. These are real in-the-trenches practitioners, and it was great to hear the different ways people approach similar problems. Most of the attendees gave technical presentations, all of which were interesting both in themselves, and in the discussions they generated. The highlight though was our "technical assignment". The Power Tools will be familiar to most Tridion specialists as a set of custom pages which use the API to make some common tasks more manageable. Now that GUI extensibility is a first class citizen, it makes sense to review the old power tools and re-implement them in the new idiom. Chris Summers had prepared the ground before the retreat, and once he had led us through his analysis, the team began on a re-implementation of the batch image uploader.
The new power tools are now hosted on Google Code, and in addition to beginning the work of re-vitalisation, the MVPs and Community Builders also spent time to set up the basis of an open-source development team to continue the work once we all got back home.
I'd like to thank Tridion** for having invited me, not only for their splendid hospitality, but for the opportunity to spend some time off-line working with such stimulating people. As always - thanks to Nuno for pulling it all together, and thanks to the guys for being who they are!
** Yes - I know that I can't thank "Tridion", and that I should say "SDL Web Content Management Solutions", but everyone knows what I mean.
Announcing the Tridion Practice project
There's much talk in some circles about the "Tridion community" and how we can promote community contributions and collaboration. I've been thinking about this, and I see a "gap in the market". Tridion's own building blocks exchange is great, and there are some outstanding community contributions on there. It's a showcase for people's coding talents and much more, but when it comes to making use of the building blocks, the most obvious way to do so is to download and install a given building block. Somehow the focus is not on showing people how they can raise their game at a coding level. The code is available, but it seems the focus is not on the code.
Other people have mentioned the need for a "cookbook" with examples and samples, but perhaps with the idea that this is something Tridion should produce. Of course, the documentation team at Tridion are putting huge efforts in to expanding the product documentation, and there are many examples to be seen there. That's not what I'm on about either.
You don't read product documentation to become inspired. (My humble apologies to the documentation team for my boorish bluntness.) But really - you don't. That's not what it's for. Then of course, there's other coders' blogs. Some of the Tridion bloggers are great, and you do sometimes get the sense of "Hey guys! This is cooool!! Did you know you could do this!!!! ???". That's something we need more of.
So you're probably ahead of me here, but the "gap" I talked of - what's missing - is a community collaboration cookbook. I'm announcing today that I have begun that project, and that anyone who wishes to help is welcome to join me. I believe that by collaborating, we can produce an on-line resource that combines real inspiration with sheer grinding competence. I've chosen to host it on "neutral" territory, at Google Code (https://code.google.com/p/tridion-practice/). I've also selected a liberal open source license (MIT) for the project, because I don't want anyone to have a moment's doubt as to whether it's OK to copy and paste the code straight in to their own work.
I've begun the cookbook with a single recipe: a template building block that factors some of the work out of building page templates. I hope this inspires you (OK - I'm smiling as I write this!) but if it doesn't please don't let that put you off. Maybe it's just not your thing, or you have a completely different approach to the same problem that works fine for you. In that case, maybe one of the other recipies will float your boat (assuming that more recipies will come).
Alternatively, you may look at it and think: "Gee, that would be great, except that such a thing irritates me, or I don't see why that part of the explanation is like that, or sheesh that code is ugly, or why aren't there any comments", or whatever. I know you're definitely ahead of me this time, but I'm going to say it anyway; this is where you come in. Community collaboration doesn't have to be about spending all your free evenings for a week producing a whole recipe yourself. You can help to improve it simply by sending in a couple of review comments, or whatever contribution suits you. Part of the reason for hosting it on neutral territory is to reinforce the idea that criticism is welcome.
Then again, you may not wish to contribute directly. That's also fine. Please make use of whatever you find on the Tridion Practice project. Enjoy! Be inspired! :-)
How does SDLTridion rate on J. Boye's 8 CMS features you are likely never to use?
CMS industry analyst Janus Boye just posted "The 8 CMS features you are likely never to use". Generally, I agree with Janus's 8 points, and I definitely agree with the thinking behind it; that it's important to know which features are relevant to you when choosing a CMS. Here's my point-by-point take on how this applies to Tridion:
Dominic: Indeed, the vast majority don't use it, and I'd be the first to advise them not to. Unless, of course, they need it. Working with workflow is always going to be more complex than working without it, but if you have a governance requirement which demands it, you have to have it. Tridion can meet this need, but I'm very glad most implementations don't need it.
Dominic: This feature is available in Tridion, and to be honest, I don't know how much it gets used by content workers. For developers, I'd say it gets used pretty regularly.
Dominic: At one time or another, Tridion has had built-in integrations with Word. Over the years, people have realised that copy and paste works just as well, and they understand it better. Most implementations have some code in the format area XSLT for cleaning up the MSO "crap" that comes along with the paste. I don't know of anyone these days using an explicit integration, (or even if it's still on the truck at Tridion).
Dominic: You could implement this easily enough in Tridion with maybe some BluePrinting and an extra publication target. On the other hand, I have never, ever, been asked for this.
Dominic: Totally agree. What? Like the content teams have got too much time on their hands. Gee, we musta been good! :-)
Dominic: Down the years, Tridion has been a "best of breed" WCMS. The vision was always to have great APIs, so that Tridion itself could integrate well with other specialised software. (In other words: have some focus; stick to what you're good at.) It's not uncommon to feed data to a search engine at publish time, although just having your search appliance spider the site is probably just as good. So advanced search is often a requirement on Tridion projects, but you don't usually use Tridion itself for it. Of course, these days, we have advanced taxonomy support, and some search-like features might well be driven by categorising content on the back end.
Dominic: Indeed - more usually done with a third-party tool. It's not core WCMS functionality. You can definitely integrate it via some of Tridion's out of the box features (e.g. target groups, customer characteristics) but it's not what a WCMS is for.
Dominic: In Tridion, this is called SiteEdit, and these days it's pretty much an expected part of an implementation, perhaps even more so with big customers. I think Janus is missing the point a little here. You need a staging environment for this approach to make sense, because that's where you use it. You certainly don't miss out permissions and quality assurance. With Tridion SiteEdit, you still need the requisite permissions, and your quality assurance process stays intact.
Keep the good stuff coming Janus. Maybe some other people will give a detailed breakdown for their favourite CMS.
Republish from publish queue for Tridion 2011
Many of you will remember that absolutely the most popular Tridion extension ever :-) was my Republish from publish queue extension. It has just come to my attention that Bart Koopman has implemented pretty much the same thing for Tridion 2011.
First of all - it's great to see this old chestnut get a new lease of life. Thanks Bart.
Just in case anyone thinks Bart nicked my idea - well he didn't. We were both inspired by Hendrik Tredoux's idea which he posted on the ideas site way back. (Actually the "most voted" idea on the site to this day.) The irony of it is that although Hendrik was the one who posted the idea - he could probably have implemented it in his sleep, and the people of whom that is true would make a very short list indeed. Hopefully, with the great extensibility API in 2011, the list will be much longer.
But back to the main theme. That's the great thing with a software community; we're all throwing ideas around and bouncing off each other. I love it. The really good part is that right now I'm just a tad too busy to re-implement this extension for 2011 (it was on my to-do list - honest!) and now I don't have to. I suppose I should at least find the time to download Bart's extension and kick the tyres. :-)
Using powershell to do useful things with XML lists from Tridion
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