Showing posts with label Tridion. Show all posts
Showing posts with label Tridion. Show all posts

Thursday, August 19, 2010

VBScript to detect if there is a COM+ application instance running

I had to figure out how to query the COM Administration catalog to detect if there is a specified COM+ application running.
Here is the quickly written vbscript:

running = False
Dim oCatalog
Set oCatalog = CreateObject("COMAdmin.COMAdminCatalog")
sName = "Tridion Content Manager"
Dim result
set apps = oCatalog.GetCollection("Applications")
apps.Populate

For Each app In apps
  If app.Name = sName Then
    Set objAppInst = apps.GetCollection("ApplicationInstances",app.Key)
    objAppInst.Populate
    for each inst in objAppInst
      pId = inst.Value("ProcessID")
      running = not ( inst.Value("IsPaused") or inst.Value("HasRecycled"))
      if running then
        Exit For
      end if
      Set objAppInst = nothing
    Next
    Exit For
  End If
Next
set apps = Nothing
set oCatalog = nothing
MsgBox pid & " " & running

Wednesday, August 11, 2010

Migrating Tridion templates from 5.2 to 2009

Upgrading a Tridion environment from Tridion 5.2 SP1 to Tridion 2009 SP1 isn’t a straightforward procedure, especially when you have to cope with a lot of custom functionalities. In this post, I write down some experience whith the upgrade of the old templating framework to a new one.


Tridion 5.2 situation


The templating framework used in 5.2 (pre-compound templating) was we built as a ScriptExtension in C#, and this ScriptExtension was registered one in the Tridion MMC snap-in. All the present VBScript based templates call this ScriptExtension with some templating parameters. This templating setup is very easy to deploy (register one dll and you're ready to go),


The drawback of this solution was the usage of the Tridion COM API, and the managed to native interop issues you have to deal with (like releasing your references to COM objects), but with Tridion 5.2 (and before that, with Tridion 5.1SP4) it worked.


First try, migrating this solution to 2009 SP1


While building our solution with Tridion 5.1SP4 and 5.2SP1, you had to built the Interop assemblies yourself. With version 2009, Tridion delivered these in the bin\client folder of the Tridion installation. Linking the existing solution to these new assemblies, fixing small API changes and recompiling the whole worked like a charm. But testing the code gave some major issues. The internals of the deployment packages changed for binaries and was killing the custom metadata for indexing purposes. But the whole publishing process became a large memory hog. So it was needed to rewrite the solution (or restart the TDSE process once in a short while). That was basically what was done, the legacy rendering and publishing was ditched for the new way of publishing and rendering content.

Wednesday, September 2, 2009

New VS2010 / .NET 4.0 features

I already love the new dynamic keyword in VS2010, and missing it in my day to day work with Tridion COM objects. Although the drawback is no IntelliSense in VS on the object, see the following code to get the item title from a Tridion TOM item:

Old code

object obj = tdse.GetObject(tcmUri, EnumOpenMode.OpenModeView, null, XMLReadFilter.XMLReadAll );
string title = null;
if (obj is Component)
{
Component component = (Component) obj;
title = component.Title;
}
if (obj is Folder)
{
Folder folder = (Folder) obj;
title = folder.Title;
}
if (obj is Page)
{
Page page = (Page) obj;
title = page.Title;
}


New code

dynamic tridionItem = tdse.GetObject(tcmUri, EnumOpenMode.OpenModeView);
string title = tridionItem.Title;


Another long-missing-but-finally-added feature is the support of optional COM Interop parameters in C#. Before 4.0, you needed to specify all optional parameters in Interop method calls, even if you wanted to use the default values, leading to extremely long method calls (maybe not in Tridion, but infamous in Office Interop with Type.Missing). These method parameters do now have the block parentheses around them in IntelliSense:

Saturday, May 2, 2009

Code generation for a COM wrapper assembly

For my last post, I needed some quick and dirty code generator. Basically, I've a COM Iterop assembly full with interfaces I need to wrap with a specified template.

The following code works, but still needed some handwork (like hand coding the ref parameters). Use it on your own risk ;)


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;

namespace CodeGeneration
{
class Program
{
static Dictionary<string, string> keywordType;

static void Main(string[] args)
{
FillKeywordType();

Assembly interop = Assembly.Load(@"Tridion.ContentManager.Interop.cm_tom");
foreach (Type t in interop.GetTypes())
{
//Console.WriteLine(t.Name);
if (t.Name.StartsWith("_"))
{
WriteOut(t, t.Name.Replace("_",string.Empty));
}
}
Console.ReadLine();
}

private static void FillKeywordType()
{

keywordType = new Dictionary<string, string>();
keywordType.Add("System.String", "string");
keywordType.Add("System.Void", "void");
keywordType.Add("System.Int32", "int");
keywordType.Add("System.DateTime", "DateTime");
keywordType.Add("System.Boolean", "bool");
keywordType.Add("System.Object", "object");
}

private static void WriteOut(Type t, string className)
{
Console.WriteLine("Info of {0}", t.Name);

using (StreamWriter writer = new StreamWriter(string.Format("D:\\Work\\DisposableOut\\{0}.cs", className) ))
{

WriteHeader(writer, className);


foreach (MethodInfo mi in t.GetMethods())
{
if (!(mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_") || mi.Name.StartsWith("let_")))
{
string methodSig = string.Format("{1} {0}", mi.Name, GetTypeName(mi.ReturnType.Name, mi.ReturnType.Namespace));
bool isVoid = (GetTypeName(mi.ReturnType.Name, mi.ReturnType.Namespace)=="void");
List<string> parameters = new List<string>();
List<string> parameterNames = new List<string>();
foreach (ParameterInfo pi in mi.GetParameters())
{
parameters.Add(string.Format("{1} {0}", pi.Name, GetTypeName(pi.ParameterType.Name, pi.ParameterType.Namespace)));
parameterNames.Add(pi.Name);
}
string parameter = string.Join(", ", parameters.ToArray());

writer.Write(@"public {0} ({2})" + Environment.NewLine +
@" {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" {5}com{1}.{3}({4});" + Environment.NewLine +
@" }}" + Environment.NewLine + Environment.NewLine,
methodSig, className, string.Join(", ", parameters.ToArray()), mi.Name, string.Join(", ", parameterNames.ToArray()),
isVoid?string.Empty:"return ");
}
}

foreach (PropertyInfo pi in t.GetProperties())
{
writer.Write(@"public {0} {1} {{" + Environment.NewLine, GetTypeName(pi.PropertyType.Name, pi.PropertyType.Namespace), pi.Name);
if (pi.CanRead)
{
//get
writer.Write(@" get {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" return com{1}.{0};" + Environment.NewLine +
@" }}" + Environment.NewLine ,
pi.Name, className);
}
if (pi.CanWrite)
{
//set
writer.Write(@" set {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" com{1}.{0} = value;" + Environment.NewLine +
@" }}" + Environment.NewLine,
pi.Name, className);
}
writer.Write("}" + Environment.NewLine + Environment.NewLine);
}

WriteFooter(writer);
writer.Flush();
writer.Close();
}
}

private static string GetTypeName(string name, string nameSpace)
{
string typeName = string.Format("{0}.{1}", nameSpace, name);
if (keywordType.ContainsKey(typeName))
{
return keywordType[typeName];
}
return typeName;
}

private static void WriteFooter(StreamWriter writer)
{
writer.Write(@" #endregion" + Environment.NewLine +
@" }" + Environment.NewLine +
@"}" + Environment.NewLine);
}

private static void WriteHeader(StreamWriter writer, string className)
{
StringBuilder fileHeader = new StringBuilder();
fileHeader.AppendFormat(@"using System;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Collections.Generic;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Text;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Runtime.InteropServices;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using Tridion.ContentManager.Interop.TDS;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@"namespace DisposableTridion{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" public class {1} : DisposableBase {0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" private Tridion.ContentManager.Interop.TDS.{1} com{1};{0}", Environment.NewLine, className);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" public {1}(Tridion.ContentManager.Interop.TDS.{1} {2}){0}", Environment.NewLine, className, className.ToLower());
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" com{1} = {2};{0}", Environment.NewLine, className, className.ToLower());
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" ~{1}(){0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" Dispose(false);{0}", Environment.NewLine);
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" protected override void DisposeMembers(){0}", Environment.NewLine);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" DisposableBase.ReleaseComObject(com{1});{0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" #region _{1} Members{0}", Environment.NewLine, className);

writer.Write(fileHeader.ToString());
}
}
}

Friday, May 1, 2009

Disposing COM objects

The Tridion solutions at my current assignment are working great, but now and then the CM website freezes. After investigating the issue with Tridion customer support, there is a suspicion the Tridion COM objects freeze under memory load.
This load is generated because our custom solutions do not release the COM objects they use. So now the codebase should be recoded with this cleanup operation.

When you don't cleanup this code, the cleanup will be initiated by the .NET Garbage Collector (GC). But the GC isn't working continually, but will only be called under memory pressure.
Because COM objects aren't run under as managed code, the GC doesn't know how much memory is used by them. Therefore, waiting until the GC will cleanup your code can take a long time, especially because COM objects are small objects on the managed memory.

The pattern to use for cleaning up unmanaged resources is the IDisposable interface. So with this pattern, we can release a COM object after you're done with it by calling the Marshal.ReleaseComObject method.

Because the Tridion Object Model (TOM) has a lot of objects, I'm creating a managed wrapper assembly, where on each object I can implement the IDisposable interface. Besides the interface, I need some more plumbing, so the first class to create is an abstract DisposableBase class:

public abstract class DisposableBase : IDisposable
{
private bool disposed = false;

protected void checkNotDisposed()
{
if (this.disposed)
{
string message = "Object of type " + base.GetType().Name + " cannot be accessed because it was disposed.";
throw new ObjectDisposedException(base.GetType().Name, message);
}
}

public void Dispose()
{
if (!this.disposed)
{
this.Dispose(true);
}
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.DisposeMembers();
}
this.disposed = true;
}

protected abstract void DisposeMembers();

public static void ReleaseComObject(object interopObject)
{
if (interopObject != null)
{
Marshal.ReleaseComObject(interopObject);
}
interopObject = null;
}
}



So I can use this base class in each wrapper class for TOM classes, like the Component class:

public class Component : DisposableBase
{
private Tridion.ContentManager.Interop.TDS.Component comComponent;

public Component(Tridion.ContentManager.Interop.TDS.Component component)
{
comComponent = component;
}

~Component()
{
Dispose(false);
}

protected override void DisposeMembers()
{
DisposableBase.ReleaseComObject(comComponent);
}

public void CheckIn(bool permanentLock)
{
checkNotDisposed();
comComponent.CheckIn(permanentLock);
}

_Component Members
}


So now it's perfectly safe to use the TOM to check in a component with the following code:

using (Component component = tdse.GetObject("tcm:8-123", EnumOpenMode.OpenModeView))
{
component.CheckIn(true);
}

Friday, January 16, 2009

Installing Windows 7

StartingW7I’ve installed Windows 7 last week. Unfortunately I hadn’t much time to play with it because we had a serious escalation on my work. The intranet server was performing badly, so I did a crash course performance counters for ASP.NET and now know that the caching service is an essential component for the Tridion Content Management system.

For improving ASP.NET Performance, the following articles helped:

InstallingW7

Monday, November 12, 2007

PowerShell Shutdown COM+

To shutdown a COM+ object, so you can redeploy the binaries it uses, I had a VBS script to do it. I wondered how I could do it in PowerShell, but lacked time to do so. I just stumbled upon some code to do it, though.

The old script was:

Dim oCatalog ' As COMAdmin.COMAdminCatalog
Dim sName 'As String
Dim sMachine 'As String
Set oCatalog = CreateObject("COMAdmin.COMAdminCatalog")
sName = "Tridion Content Manager"
sMachine = "tridionserver"

oCatalog.Connect sMachine
oCatalog.ShutdownApplication sName



And this became:


$TridionAppName = "Tridion Content Manager"
$TridionMachine = "tridionserver"

$comAdmin = New-Object -comobject COMAdmin.COMAdminCatalog
$comAdmin.Connect($TridionMachine)
$comAdmin.ShutdownApplication($TridionAppName)


(Just leave the line with the call to connect out to run this on your local machine).