In the last post on this topic we defined an attribute for a <script> task. Note: You can find the file, as we last left it, attached to the previous post if you want to go grab a copy of it for reference. The finished file for this post is attached at the bottom of this post.
ImportStrings parameters
The ImportStrings method has 4 parameters, so let's get each of these declared and configured within the ExecuteTask function code.
1. The path to the file containing the strings to import - a string
To create a file containing strings to import just open a plain text file. At the beginning of each line write the string ID, put a tab, then put the string. Do not surround the string in quotes. Make sure to use a tab and not a space. The string must all be on the same line in the file. The end of the file should be at the end of the last line. If there is an extra CRLF in the file ImportStrings won't like it.
We've already declared a task attribute to hold this value: importfile.
And in our ExecuteTask function, the importfile attribute's value gets placed into the _importfile string that we've declared.
2. The langauge ID of the particular string table (e.g. are these English, French or Italian strings?) - a string
If you only have an English string table in your project, then the language ID will be "1033". If you open the ISM in the IDE and look in the Direct Editor at the ISString table, the ISLanguage_ column is the langauge ID. Remember that this value is a string even though the content is all digits.
I didn't create a task attribute for this value because I'm always using the English string table. However you could easily declare another task attribute just the same way you did for the importfile.
Instead, in the ExecuteTask function I declared and set the _langaugeID string to "1033" right from the beginning.
3. Whether or not to overwrite existing strings of the same ID - a reference to an object
Now things get a little weird. There is a special enumerator defined just for this ieoverwrite value. If you were in Visual Studio you could see this enumerator by the title "SAAuto12.ImportStringTypes". The two possible values are eiIgnore or eioverwrite. One means to overwrite strings with the same ID, and the other means to ignore duplicate string IDs and skip them.
The task attribute for this value is declared as a string: overwrite.
I also declared a string in the ExecuteTask function called _overwrite.
The problem is that somehow this string needs to get converted to an object reference for the specific ImportStringTypes object. I tried several ways of doing this (including trying to declare the task attribute itself as an object instead of a string) and wasn't able to find a simpler method. So this may be a bit of a kluge, but it works.
In the ExecuteTask declare an object of the ImportStringTypes type and set it to a default value of overwrite. I'd do this before the project.OpenProject method is called.
object _eiType = ImportStringTypes.eioverwrite;
Now make a switch block that takes the string "true" or "false" value for the _overwrite variable and converts it to the corresponding enum value. Make the default value = overwrite.
switch (_overwrite)
{
case "true":
_eiType = ImportStringTypes.eioverwrite;
break;
case "false":
_eiType = ImportStringTypes.eiIgnore;
break;
default:
_eiType = ImportStringTypes.eioverwrite;
break;
}
When we actually use this variable in the ImportStrings method, we will use it as "ref _overwrite" so that we get a reference to it, as required.
Note: If you look at the IS help for this method it says that the overwrite parameter is optional. This works when I use a simple VBscript to run it. Unfortunately, I tried leaving this parameter off the ImportStrings function call when calling it from this C# code and it always failed with a message that the parameter was required. So... I always provide it. *shrug*
4. The path to write out the log file - a reference to an object
This is simply the path to which you want a log file created, along with the log file name. However this parameter has some of the same funkiness as the overwrite does -- it needs to be an object.
The task attribute is already declared as a string: logfile
The ExecuteTask variable is also a string as: _log
Again, to get our conversion to an object, declare an object inside the ExecuteTask method and set it equal to the _log string. I'd do this before the project.OpenProject method is called.
object _logfile = _log;
When we actally use this variable in the ImportStrings method we will use it as "ref _logfile" so that we get a reference to it, as required.
Note: If you look at the IS help for this method it says that the log file parameter is optional. Just as with the overwrite parameter, I got errors unless I provided it.
Basic Error Handling
Before we attempt to open the project, let's cover a few error conditions and handle them.
Let me state right now that I've been told this is not 'proper' C# coding - it's not best practice to use try/catch blocks in this way if you are doing full on C# classes and whatnot. Alright, so be it. I'll clean it up and make it pretty later :-) But for now, it works and seems adequate for this usage. So here we go.
Right after declaring our project object, start a try block. Inside that block we will test for a few different error conditions. Any error will throw us into the catch block where we log an error to nAnt providing it with the specific error condition, then issue a single task-level nAnt task error to indicate that our custom task has "failed".
ISWiProject project = new ISWiProject();
try
{
...set of error handlers described below goes here...
project.SaveProject();
project.CloseProject();
}
catch (Exception ex)
{
project.CloseProject();
Log(Level.Error, "ERROR: Cannot import strings. {0}", ex.Message);
throw new BuildException("Cannot import strings");
}
In these error checks I've used the String.Format method in order to be able to insert the relevant variable's value into the error message. You'll see that String.Format lets you give a string, then put placeholders in for each of the items you want to insert into the message, such as {0} {1} {2}. Then just give each of the items to replace separated by commas.
1. Error: The import file doesn't exist
Make sure that the file exists with a simple call to the System.IO.File.Exists method. If it fails, throw an error.
if ( !System.IO.File.Exists(_importfile) )
{ throw new Exception(String.Format("Cannot locate import file {0}." , _importfile )); }
2. Error: The installer project file doesn't exist
Make sure that the ISM file exists, same as with the import file.
if ( !System.IO.File.Exists(_ismpath) )
{ throw new Exception(String.Format("Cannot locate ISM {0}." , _ismpath)); }
3. Error: The installer project exists, but it is locked or otherwise unable to be written to
In this case the OpenProject method will not return an error, so unless we check for it we won't realize that the project is successfully opened, but can't be written to. Open the project and check the return value. Any value other than zero is an error for us.
if ( project.OpenProject(_ismpath, false) != 0)
{ throw new Exception(String.Format("Failed to open ISM {0}." , _ismpath)); }
4. Error: The ImportStrings method fails in some way
We actually call the ImportStrings method now, but check to make sure it worked. We use the 4 parameters that we defined and configured above.
if ( !project.ImportStrings(_importfile, languageID, ref _eiType, ref _logfile) )
{ throw new Exception("Cannot perform import"); }
And that's all she wrote!
This should be a functioning custom task for importing strings. Hope this little exercise is useful to you in some way.