1

I want to be able to change a value in one place in my C# .NET 4.0 Project. For this I use the built in Properties/Settings.settings file, which is essentially an XML file.

As we're using InnoSetup (which is pretty common) for our software, and as the settings.settings file is the default way of storing values for a C# .NET application, I wondered if there was a way for the InnoSetup script to pull values from the settings file, or if you could point me to a script that could do this to set variables of the setup script.

EDIT:

I got the XML example running, but now I cannot use an XPath query to get the correct node. This is my script:

[Code]
{--- MSXML ---}

const
  XMLFileName = '..\CCFinderWPF\Properties\Settings.settings';
  XMLFileName2 = '..\CCFinderWPF\Properties\Settings.xml';

function Settings(Default: String): String;
var
  XMLDoc, SeekedTopNode, SeekedNode, iNode, Sel: Variant;
  Path, XPath: String;
begin
  { Load the XML File }
  try
    XMLDoc := CreateOleObject('MSXML2.DOMDocument.4.0');
  except
    RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)');
  end;
  XMLDoc.async := False;    
  XMLDoc.resolveExternals := false;
  XMLDoc.preserveWhiteSpace := true;
  XMLDoc.setProperty('SelectionLanguage', 'XPath');
  XMLDoc.load(XMLFileName);
  if XMLDoc.parseError.errorCode <> 0 then
    RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason);

  MsgBox('XML-File: ' + XMLFileName, mbInformation, MB_OK);
  { Modify the XML document }
  iNode   := XMLDoc.documentElement;
  iNode := iNode.selectSingleNode('Setting[@Name="' + Default + '"]');
// **selectSingleNode returns null, seems that selectSingleNode with XPath doesn't work?**
  MsgBox('The Node is: ' + iNode.nodeName, mbInformation, MB_OK);

  SeekedNode := iNode.firstChild;
  Result := SeekedNode.lastChild.text;
  MsgBox('The XPath is: ' + XPath, mbInformation, MB_OK);
end;

I call this function using the Inno Setup Precompiler like this:

#define ABAppName "{code:Settings|AppName}"

The XML-file looks like this:

<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="CCFinder.Properties" GeneratedClassName="Settings">
  <Profiles />
  <Settings>
    <Setting Name="AppName" Type="System.String" Scope="Application">
      <Value Profile="(Default)">CCFinder</Value>
    </Setting>
    ...

The goal of all this is that I can set values in my app from the Settings.settings file in my C# projects, have my hudson instance checkout my code and change this XML file for different versions of the app and for example change some values that I don't necessary know at design time. I want to be able to pull my variables from this XML file. I'm just stuck using MSXML2 here. Any help would be greatly appreciated.

Akku
  • 4,373
  • 4
  • 48
  • 67
  • 1
    As the file is XML, there are already [many](http://stackoverflow.com/questions/8141886/inno-setup-modify-xml-file-based-on-custom-input) [questions](http://stackoverflow.com/questions/8194209/adding-a-node-to-an-existing-xml-file-using-inno-setup) that ask the same [basic question](http://stackoverflow.com/search?q=%5Binno-setup%5D+%22xml%22) – Deanna Nov 24 '11 at 09:01
  • While your hints look good at first sight, they actually don't lead to anything. The best hint in all these links was "do it with another app or dll that you call with inno setup". Anything more specific? – Akku Nov 24 '11 at 09:07
  • 1
    You missed [the answer](http://stackoverflow.com/q/8149066/588306) about using the MSXML library (with an example) to do it. – Deanna Nov 24 '11 at 09:09
  • Okay, I'm trying that out. If I get this done, I'll post it here. Thanks! – Akku Nov 24 '11 at 09:49
  • I'm just trying out the example with the XML loading. This works. Is there a way of setting constants using this code, so that I can for example say [Setup] AppCopyright={Company} AppPublisher={Company} VersionInfoCompany={Company} VersionInfoDescription={Company} VersionInfoCopyright=Copyright by {Company} – Akku Nov 30 '11 at 13:40
  • Sorry, I don't see how updating a .NET settings file has any bearing on compile time values of an Inno setup script... – Deanna Nov 30 '11 at 15:30
  • I want to use values from the Settings file, not update them there. My single only true value lives in the Settings file, and I want the setup to draw it in. Because the setup-scripts are compiled in Hudson after the code as been checked out. So I only need to update the xml file to have the correct values in my code AND in the setup script. (Hope this explains it, but I also left a bounty here in hope of help). – Akku Dec 05 '11 at 10:25

1 Answers1

1

Nobody knew a solution, so I solved this with a while loop. Note that this is my first Delphi code, so it might not be elegant and / or 100% correct. This is how I define the needed constants:

#define ABAppName "{code:Settings|AppName}"
#define ABAppVersion "{code:Settings|AppVersion}"
#define ABCompany "{code:Settings|CompanyName}"
#define ABAppTitle "{code:Settings|ApplicationTitle}"
#define ABAppTitlePro "{code:Settings|ApplicationTitle)}"+" "+"{code:Settings|ProVersionAppender}"
#define ABCompanyUrl "{code:Settings|CompanyUrl_de}"
#define ABContactEmailDe "{code:Settings|ContactEmail_de}"
#define ABYear "{code:Settings|Year}"

These are in a constants.iss file as plain text that I include in my setup-script with #include "constants.iss" in the beginning. This is the delphi-code that is called by the code-calls:

const
  XMLFileName = '..\CCFinder\Properties\Settings.settings';

function Settings(Default: String): String;
var
  XMLDoc, iNode: Variant;
  Path: String;
  i : Integer;
  Loop : Boolean;
begin
  { Load the XML File }
  try
    XMLDoc := CreateOleObject('MSXML2.DOMDocument.6.0');
  except
    RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)');
  end;
  try
  XMLDoc.async := False;
  XMLDoc.resolveExternals := false;
  XMLDoc.load(XMLFileName);
  if XMLDoc.parseError.errorCode <> 0 then
    RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason);

  iNode := XMLDoc.DocumentElement;
  iNode := iNode.LastChild;
  Loop := True;
  i := 0;
  while Loop do
  begin
    Try
        if iNode.ChildNodes[i].attributes[0].nodeValue = Default
        then
            begin
                Result := iNode.ChildNodes[i].text;
//              MsgBox('Result for Default: ' + Result + Default, mbInformation, MB_OK);
                i := i+1;
                Loop := False;
                Break;
            end
        else
            begin
                i := i+1;
                if i = 100 then Loop := false;
            end;
    except
        Result := '';
    end;
  end;
  except
  end;
end;
  • The loop is limited by 100 runs, because I'm too clumsy to count the XML nodes in Delphi.
  • The InnoSetup Preprocessor needs to be installed.
  • Inno doesn't seem to accept constants in path names, therefore some values just could not be drawn from the Properties/Settings.settings file.
  • I didn't know that the preprocessor doesn't actually use the Delphi code to fetch the correct values to include them in the script, instead it includes the complete delphi code call in all places where the constant is used. Of course this isn't what I wanted to do, as it doesn't even work when the code is embedded into paths for example. Therefore I changed the setup script to only include one constant I needed to replace by find and replace, and that is used for pathnames and the executable filename for example.
  • For the rest, I wrote a .NET command-line application that does basically the same thing as the delphi code, to execute it in the Hudson before the setup is compiled on Hudson. This replaces the code-calls with the actual values needed (only except, but you should get the point):

    {
    XmlDocument xmldoc = new XmlDocument();
    xmldoc.Load(pathToSettingsFile);
    
    XmlNodeList list = xmldoc.GetElementsByTagName("Setting");
    foreach (XmlNode node in list)
    {
         string keystring = node.Attributes["Name"].InnerText;
         string valuestring =  node.FirstChild.InnerText;
         settingValues.Add(keystring,valuestring);
    }
    
    var t = File.OpenText(constantsfilename);
    string text;
    using (t)
    {
        text = t.ReadToEnd();
    }
    string sample = "{code:Settings|";
    char endsign = '}';
    while (text.Contains(sample))
    {
        int startindex = text.IndexOf(sample);
        int endindex = text.IndexOf(endsign, startindex);
        int variableIndex = startindex + sample.Length;
        string variable = text.Substring(variableIndex, endindex - variableIndex);
        text = text.Replace(sample + variable + endsign, newVal(variable));
    }
    
    var w = new StreamWriter(constantsfilename);
    using (w)
    {
        w.Write(text);
        w.Flush();
    }
    }
    
    public static string newVal(string variable)
    {
        if (settingValues.ContainsKey(variable))
            return settingValues[variable];
        else
        {
            return "";
        }
    }
    

This means I now can set values in my Settings-File that can be changed with yet another command-line application in hudson for special builds, and gets automatically written to the setup script.

EDIT: Just realized that it's the Visual Studio Designer that manually transfers the settings from the settings-file to the app.config, which gets changed to ProgramName.exe.config file in the output while building. Therefore you needs to change the values in there to have it have an effect, the coe should work if you give it the correct path to the file.

EDIT2: This app.config is renamed during compilation into YourAppName.exe.config ... when you're not putting it inside the setup, the values are not initialized correctly. I extended my code to also adapt the designer code of the Settings.designer.cs file with the values from the XML and guess this will do it. One configuration file to rule them all :-)

Akku
  • 4,373
  • 4
  • 48
  • 67