Question

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.

Was it helpful?

Solution

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 :-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top