2009-10-16

Converting word doc to pdf in CruiseControl.Net

Today I needed to automate the process where we create PDF files out of our client documentation in Microsoft Word format. Since up to now we have done it manually by opening the document and printing to a installed PDF printer. Quite tedious work.
After some searching I start to realize that there are many PDF creators around; and most of them uses Ms word and prints using a pdf printer (just as we use to do, but by automation).
The problem is that I don't want to install printers or the Ms Office package on our build server (if I can avoid it). Another wish is that the solution should be free and open source.
So finally I stumbled upon this article by Graham J. Williams that uses OpenOffice.org to do the conversion and a custom macro to allow the conversion to be called from the commandline.

REM  *****  BASIC  *****

Sub ConvertWordToPDF(cFile)
cURL = ConvertToURL(cFile)

' Open the document.
' Just blindly assume that the document is of a type that OOo will
' correctly recognize and open -- without specifying an import filter.
oDoc = StarDesktop.loadComponentFromURL(cURL, "_blank", 0, Array(MakePropertyValue("Hidden", True), ))

cFile = Left(cFile, LastIndexOf(cFile,".")) + "pdf"
cURL = ConvertToURL(cFile)

' Save the document using a filter.
oDoc.storeToURL(cURL, Array(MakePropertyValue("FilterName", "writer_pdf_Export"), ))

oDoc.close(True)

End Sub

Function LastIndexOf ( cText as String, cMatch as String) As Integer
lastIndex = 0
pos=0
Do
pos = InStr(pos+1,cText,cMatch)'
If pos >0 Then
lastIndex = pos
End If
Loop While pos >0
LastIndexOf()=lastIndex
End Function

Function MakePropertyValue( Optional cName As String, Optional uValue ) As com.sun.star.beans.PropertyValue
Dim oPropertyValue As New com.sun.star.beans.PropertyValue
If Not IsMissing( cName ) Then
oPropertyValue.Name = cName
EndIf
If Not IsMissing( uValue ) Then
oPropertyValue.Value = uValue
EndIf
MakePropertyValue() = oPropertyValue
End Function
I modified it a bit to allow correct handing of extensions.
To call it from a windows command prompt just enter
c:\program files\OpenOffice.Org 3\Program\swriter.exe -invisible "macro:///Standard.Module1.ConvertWordToPDF(c:\temp\My word document.doc)"
I incorporated it in our cruisecontrol.net nant scripts so all solutions that needs pdf conversions in the build chain can have it.
Great!

2010-05-11 Update: Fixed a bug with filenames that contained more than one period (.), added the LastIndexOf function

2009-08-06

Bugfix for Documentator Macros (VB)

A small bug fix for the auto documentation feature for VB.Net.

Thanks for the report Sam; what has it been, nearly 2 years since you reported the bug finally I've come about... ( You said it wasn't urgent... you'll probably reconsider next time ;)


When autodocumenting a Sub or function that is connected to an event, an error occurs.


The fix is included in version 2.5.3.0 that can be downloaded from http://dan.meridium.se/DocumentatorMacros/v2.5.3.0/Meridium.rar.


(For more information about the Documentator Macros, see http://www.codeproject.com/KB/cs/documentatormacros.aspx )

2009-07-27

Mvc for Winforms - Mapping the View event to the Controller action Part III

Long time since part I and II were posted, but I've been quite busy lately so I haven't found time (or prioritized) to write the last part, but here we are :)

To recap, in part I I explained about the background of the issue, that I was writing a small winforms MVC framework. The goal of the framework was to be able to instruct the controller to listen to a component in the view and when the component called an event, a specific method would be called.
For example, the call below would register the click event from the saveButton

Controller.RegisterAction(saveButton,"Save");
(in later editions I have also added support for lamda expressions instead of method names, and thus almost abandoned the previous)
Controller.RegisterAction(rotateRightButton,() => Controller.Rotate(90));
Controller.RegisterAction(rotateLeftButton,() => Controller.Rotate(-90));
In part II I wrote about how find and listen to the event of the component.
In this section I will explain how I call the method.

What we ended with in the last post was that we had registered the controllers event to call the ExecuteAction method of the Controller class.
public void ExecuteAction(object source, object eventArgs, ActionData actionData)
or in the latter case using lambda expressions (or action)
public void ExecuteAction(object source, object eventArgs, Action action)
In this case the Action is the easiest to implement. Just call the Invoke method of the Action instance and you are done.
public void ExecuteAction(object source, object eventArgs, Action action) {
action.Invoke();
}
The no Action ExecuteAction method (almost abandoned way )
The ActionData variant is somewhat more tedious but this is how ASP.NET MVC does it.
First we need to find the method to call. The ActionData contains the name of the method. We now needs to find the method of the controller that matches that name. MVC for ASP.NET uses the ActionSelectorClass. The ActionSelectorClass is responsible for retrieving the MethodInfo for an action by passing in the name of the action. It support action aliases and other things so look it up for a great example.
In short it uses reflection to get all methods, filters out irrelevant methods and returns the one that matches the name.
Example (really shortened example, lookup ActionSelectorClass in the ASP.NET MVC framework for a complete example.)
//get all methods
MethodInfo[] array = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance);
//convert to a dictionary
ILookup<string,MethodInfo> methodLookup = array.ToLookup(method => method.Name, StringComparer.OrdinalIgnoreCase);
//return the matching MethodInfo
MethodInfo action = methodLookup[actionName];
Now we have the MethodInfo to invoke, only to calculate the parameters to pass to the method is left.

The action is invoked using the Invoke method of the MethodInfo class and we need to pass the instance to invoke it on (the controller instance) and the object array containing the parameters to pass to the method/action.
Example
action.Invoke(controller,parameterValues);
If the action don't have any parameters we just pass null, but if we do have parameters we need to assign values for them.
Remember: we wanted to be able to register the call
Controller.RegisterAction(createBoldTextButton, "CreateText", new {name="Bold", type=4}); 
To call the action method
public void CreateText(string name, int type) {...}  
using the "Bold" as value for the name parameter and 4 as value for the type parameter.

To succeed with this we need to do some parsing.
  1. Iterate all parameters of the method info.
  2. Handle special cases (like if the parameter is named source and is of type object, then the source value from the event handler method should be passed).
  3. Find a property with a matching name from the value object passed in the RegisterAction.
Iterate all parameters of the method info
Using reflection it's easy to find the parameters.

List<object> objects = new List<object>();
foreach (ParameterInfo info in method.GetParameters()) {
objects.Add(GetParameterValue(info));
}
return objects.ToArray();
Handle special cases
The GetParameterValue method will return the value to pass to the supplied ParameterInfo. In some cases the value to be passed as parameter is not any of the values passed to the values parameter (in the RegisterAction method), like for example, if one would need to get the EventArgs or source parameter passed from the event component, then we would need to handle them. (other customizations can be done here, this is an example)
Type parameterType = parameterInfo.ParameterType;
string parameterName = parameterInfo.Name;

//handle eventArgs and source parameters
if (string.Equals(parameterName, "e", StringComparison.OrdinalIgnoreCase) && _eventArgs != null && parameterType.IsAssignableFrom(_eventArgs.GetType()))
return _eventArgs;
if (string.Equals(parameterName, "source", StringComparison.OrdinalIgnoreCase)&& parameterType.Equals(typeof(object)))
return _source;

//parse the values object
return GetValuesValue(parameterType, parameterName);
Find a matching property of the value object
At last we would try to find a matching property of the value object that matches the parameter type and name. (First we check that the parameter don't matches the whole value object)
//if we don't have a value, return null
if (ValueType == null)
return null;
//if the value is matching, use it as value
if (type.Equals(ValueType))
return _actionData.Values;
PropertyInfo[] valueProperties= ValueType.GetProperties();
//if the value don't have any properties, return null
if (valueProperties.Length == 0)
return null;
//find matching property
PropertyInfo[] possibleMatches = valueProperties
.Where(p => string.Equals(name, p.Name, StringComparison.OrdinalIgnoreCase)
&& type.IsAssignableFrom(p.PropertyType))
.ToArray();
if (possibleMatches == null || possibleMatches.Length == 0)
return null;
if (possibleMatches.Length > 1)
throw new AmbiguousMatchException(string.Format("The value collection contains ambigous match values for the parameter {0} {1}", type, name));
return possibleMatches[0].GetValue(_actionData.Values, null);
Ok, so now we can invoke the method with our array of parameters. (as I said, a bit tedious).
If we compare the two variants,
Controller.RegisterAction(createBoldTextButton, "CreateText", new {name="Bold", type=4});
Controller.RegisterAction(createBoldTextButton, ()=> Controller.CreateText("Bold", 4);
you understand why I have abandoned this last variant and only uses Actions/lambda functions.
The tedious method can still be used if you want to lift the connection between view and controller to a configuration layer. Then the Action method will be hard to implement.

That sums up this last part of my WinForms MVC framework lessons and experiences. Hope that you learned something.

2009-06-24

The start address <https://newhostname.domain.com> cannot be crawled

Have a SharePoint site on one of our servers and have lately added a new hostname to that server to access a new site collection. I started getting a lot of entries in the eventlog using event id 2436 stating:

The start address <https://newhostname.domain.com> cannot be crawled.

Context: Application 'Search index file on the search server', Catalog 'Search'

Details:
Access is denied. Check that the Default Content Access Account has access to this content, or add a crawl rule to crawl this content. (0x80041205)

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.


Another symptom on this error is that if you open a web browser on the server itself and try to navigate to the url, you'll only get an authentication dialog (symptom on a 401 error).

After a bit of searching I found a solution that I recognized. The following kb article http://support.microsoft.com/kb/971382 provided the solution to add the new hostname to the registry key HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\BackConnectionHostNames .

When I started regedit I found that the key already existed and a couple of hostnames already was present. It seems that I had implemented this fix before.
Better to write a post about it so I don't forget it in the future :)

Ps. Don't forget to restart the server after applying the registry change. Ds.

Export and Import Work item queries in TFS projects

I have lately been introducing new WorkItemTypes in our TFS project to handle tests and support cases. With those new types the need for modifying the queries in the current projects was necessary. To modify a query I could have opened them in visual studio and saved them to disk and then saved them individually to each project. Since we are getting a lot of projects (60+) this would be a quite tedious task to update 10+ queries in each project individually.
To solve this administrative plague I wrote a small commandline program that allows you to export and import queries from a project.

Example
To list all queries in a project
TFSQueryUtil.exe /t https://tfsserver.domain.com:8143 /p "My Tfs Project"

This will list every Work Item Query by Scope, Name and description

To export all queries to the current folder
TFSQueryUtil.exe /t https://tfsserver.domain.com:8143 /p "My Tfs Project" /o Export /q *

This will export each query using the name of the query (plus the .wiq extension) as name of the exported file. If you want to only export one query, use the example below.
TFSQueryUtil.exe /t https://tfsserver.domain.com:8143 /p "My Tfs Project" /o Export /q "My query"

To import all *.wiq files to a project as Team queries
TFSQueryUtil.exe /t https://tfsserver.domain.com:8143 /p "My Tfs Project" /o Import /f *.wiq /qs Public

This command will use the filename (without the .wiq extension) as name for the imported query.
You can also specify a Description of the query by using the /qd switch (best for use with one query imports)

The format of the wiq files follows the WorkItemQuery schema but since the schema only includes the query and no meta data (like name, scope or description) this has to be provided as parameter switches... (Perhaps something to fix in TFS 2010)

If you need more help add the /? parameter.

I haven't released the program as open source but feel free to use it if you have need for it.
Can be downloaded from http://dan.meridium.se/TfsQueryUtil.rar
To use it you need to have Team Foundation Explorer 2008 installed. (needs the tfs dlls in the GAC).

2009-09-28 Update; small bugfix
* Now writes xml files as utf-8 (was an in consequence between xml notation and file encoding)

2009-05-05

New version of DocumentatorMacros

Long time since last version was released officially... (almost two years), time to make a change.
New version can be downloaded from http://dan.meridium.se/DocumentatorMacros/v2.5.2.0/Meridium.rar
(For more information about the Documentator Macros, see http://www.codeproject.com/KB/cs/documentatormacros.aspx )

New version contains some news:

Contains support for Resharper 4.5, Visual studio 2008 and some minor changes.

Enhancements /Bugfixes

  • No longer enters wrong type of linefeeds when applying some functions
  • PasteTemplate
    • now indents correctly
    • Handles static events
    • Handles new, virtual, override keywords when converting fields->property
  • DocumentThis - Now autodocuments thrown exceptions

2009-05-04

AssemblyName.GetPublicKeyToken() ToString using lambda

I was searching for a convenient way to get the public key token from a signed assembly and present it in an ordinary string style that is found in all FQ assembly names. The problem is that the GetPublicKeyToken() method from the Assembly type returns a byte array and using ToString() isn’t that great.

I searched the net a bit and all I found was examples on how to do it with a loop and using ToString(”x2”) on every byte, or even more horrible, indexing the 8 bytes by hand...
But if I use a linq/lambda expression, this could be a nice one-liner? ’aye?!

Ok, first convert the byte array to a string array

GetPublicKeyToken().Select(x=>x.ToString(”x2”))
Then aggregate (i was trying the concat first, but that didn’t make any sense) to a single string.
.Aggregate((x, y) => x + y)
With some error handling this boils down to the following method.

#region public static string GetAssemblyPublickKeyToken(Assembly assembly)
/// <summary>
/// Gets the public key token of the supplied argument
/// </summary>
/// <param name="assembly">The <see cref="Assembly">to get the public key token for</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">If <paramref name="assembly">is null.</exception>
public static string GetAssemblyPublickKeyToken(Assembly assembly) {
if (assembly == null) {
throw new ArgumentNullException("assembly");
}
byte[] token = assembly.GetName().GetPublicKeyToken();
if(token == null token.Length==0)
return null;
return token.Select(x => x.ToString("x2")).Aggregate((x, y) => x + y);
}
#endregion

2009-01-21

RadioButton helper and label

When using Mvc (beta) and rendering RadioButtons using the HtmlHelper they are rendered without any text or description. The label tag is used for this but is not rendered by the RadionButtons method.
Example;

<%=Html.RadioButton("MyRadioButton","MyValue")%>
renders
<input id="MyRadioButton" value="MyValue" name="MyRadioButton" type="radio">
To get the label we just add the label tag and uses the input tags id as reference for the label.
<label for="MyRadioButton">My description</label>
This will allow us to check the radio button by clicking on the label as well.

Then the problem occurs if we should use multiple radio buttons with the same name.
Example;
<%=Html.RadioButton("IsItRaining","Yes")%>
<%=Html.RadioButton("IsItRaining","No")%>
This will render
renders
<input id="IsItRaining" value="Yes" name="IsItRaining" type="radio">
<input id="IsItRaining" value="No" name="IsItRaining" type="radio">

This it not so well since we need different id attributes to be able to bind the label tags.
To fix this we need to add different ids to the input tags.
Example:
<%=Html.RadioButton("IsItRaining", "Yes", new {id="IsItRaining_Yes"})%><label for="IsItRaining_Yes">Yes</label>
<%=Html.RadioButton("IsItRaining", "No", new {id="IsItRaining_No"})%><label for="IsItRaining_No">No</label>
That will produce the intended output;
<input id="IsItRaining_Yes" type="radio" value="Yes" name="IsItRaining"/>
<label for="IsItRaining_Yes">Yes</label>
<input id="IsItRaining_No" type="radio" value="No" name="IsItRaining"/>
<label for="IsItRaining_No">No</label>
And will work as below