2

I would like to know if there is any way I can add comments in the INI file for the user to know the possible values the setting can have

For example:

My current INI file is like:

[Section]
Setting1=value
Setting2=Value

I want that to be like:

[Section]
; acceptable values for Setting1 are
; A -
; B -
Setting1=value

; acceptable values for Setting2 are
; X -
; Y -
Setting2=Value

The INI file is not fixed. Based on user selected conditions entries are added and removed from the INI file. So I cannot deploy fixed file with comments.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992

3 Answers3

1

There's no Inno Setup function nor Windows API to handle comments in an INI file.

So either you need to create whole INI file, including comments, programmatically. Or code just adding the comments; example included below.

const
  CommentPrefix = ';';

function SetIniComment(
  FileName: string; Section: string; Key: string; Comment: string): Boolean;
var
  Lines: TArrayOfString;
  Line: string;
  InSection: string;
  I, I2, P: Integer;
begin
  Result := False;

  // load INI file lines
  if LoadStringsFromFile(FileName, Lines) then
  begin
    Log(Format('Read %d lines', [GetArrayLength(Lines)])); 

    // iterate lines to look for the section and key
    for I := 0 to GetArrayLength(Lines) - 1 do
    begin
      Line := Lines[I];
      { is it a start of a section? }
      if (Length(Line) > 0) and (Line[1] = '[') then
      begin
        P := Pos(']', Line);
        if P > 0 then
        begin
          InSection := Trim(Copy(Line, 2, P - 2));
        end;
      end
        else
      // are we in "our" section
      if CompareText(InSection, Section) = 0 then
      begin
        P := Pos('=', Line);
        
        // is it "our" key?
        if (P > 0) and
           (CompareText(Trim(Copy(Line, 1, P - 1)), Key) = 0) then
        begin
          // if there's already a comment on a previous line, replace it
          if (Length(Lines[I - 1]) > 0) and
             (Lines[I - 1][1] = CommentPrefix) then
          begin
            Log(Format('Replacing existing comment on line %d', [I - 1]));
            Lines[I - 1] := CommentPrefix + ' ' + Comment;
          end
            else
          begin
            // if there's no comment yet, insert new comment line
            Log(Format('Inserting comment to line %d', [I]));
            SetArrayLength(Lines, GetArrayLength(Lines) + 1);

            for I2 := GetArrayLength(Lines) - 1 downto I + 1 do
            begin
              Lines[I2] := Lines[I2 - 1];
            end;
            Lines[I] := CommentPrefix + ' ' + Comment;
          end;

          Log(Format('Writing %d lines', [GetArrayLength(Lines)])); 
          Result := SaveStringsToFile(FileName, Lines, False);
          break;
        end;
      end;
    end;
  end;
  
  if not Result then
  begin
    Log('Section/Key not found');
  end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
var
  FileName: string;
begin
  if CurStep = ssPostInstall then
  begin
    FileName := ExpandConstant('{app}\my.ini');

    SetIniComment(
      FileName, 'Section', 'Setting1', 'acceptable values for Setting1 are A, B');

    SetIniComment(
      FileName, 'Section', 'Setting2', 'acceptable values for Setting1 are X, Y');
  end;
end;

Another thing that you can do, if you are creating a new INI file, is to deploy a template INI file with the comments. And then use the [INI] section to only modify the keys.

See How to make indents in the created INI file during installation?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
1

Try "#" prefix to mark lines as comments:

[Section]
# acceptable values for Setting1 are
# A -
# B -
Setting1=value
1

I've modified Martin's code slightly, so it's possible to delete all the notes (lines prefixed with ;) that precede the key, and it's also possible to add multiple lines of notes before each key.

const
  CommentPrefix = ';';

function DeleteIniComments(
  FileName: string; Section: string; Key: string): Boolean;
// #######################################################################################
// Deletes all comment lines preceding the Key in Section
// #######################################################################################
var
  Lines: TArrayOfString;
  Line: string;
  InSection: string;
  I, J, K, I2, P: Integer;
begin
  Result := False;
  InSection := '';

  // load INI file lines
  if LoadStringsFromFile(FileName, Lines) then
  begin
    Log(Format('INI: Delete all comments before the Key '+Key+', Read %d lines', [GetArrayLength(Lines)])); 

    // iterate lines to look for the section and key
    for I := 0 to GetArrayLength(Lines) - 1 do
    begin
      Line := Lines[I];
      { is it a start of a section? }
      if (Length(Line) > 0) and (Line[1] = '[') then
      begin
        P := Pos(']', Line);
        if P > 0 then
        begin
          InSection := Trim(Copy(Line, 2, P - 2));
        end;
      end else
      // are we in "our" section
      if CompareText(InSection, Section) = 0 then
      begin
        P := Pos('=', Line);
        
        // is it "our" key?
        if (P > 0) and
           (CompareText(Trim(Copy(Line, 1, P - 1)), Key) = 0) then
        begin
          // For all previous lines
          for J := I-1 downto 0 do
          begin
            // If line is nonempty and starting with comment prefix
            if (Length(Lines[J]) > 0) and (Lines[J][1] = CommentPrefix) then
            begin
              continue;
            end
            else begin
              K := I - J - 1;
              if (K > 0) then
              begin
                for I2 := I to GetArrayLength(Lines) - 1 do
                begin
                  Lines[I2-K] := Lines[I2];
                end;
                SetArrayLength(Lines, GetArrayLength(Lines) - K);
                Result := SaveStringsToFile(FileName, Lines, False);
              end;
              exit;
            end;
          end;
        end;
      end;
    end;
  end;
end;

function InsertIniComment(
  FileName: string; Section: string; Key: string; Comment: string): Boolean;
// #######################################################################################
// Inserts one line of Comment just before Key in Section
// #######################################################################################
var
  Lines: TArrayOfString;
  Line: string;
  InSection: string;
  I, I2, P: Integer;
begin
  Result := False;
  InSection := '';

  // load INI file lines
  if LoadStringsFromFile(FileName, Lines) then
  begin
    Log(Format('INI: Insert comment before the Key'+Key+', Read %d lines', [GetArrayLength(Lines)])); 

    // iterate lines to look for the section and key
    for I := 0 to GetArrayLength(Lines) - 1 do
    begin
      Line := Lines[I];
      { is it a start of a section? }
      if (Length(Line) > 0) and (Line[1] = '[') then
      begin
        P := Pos(']', Line);
        if P > 0 then
        begin
          InSection := Trim(Copy(Line, 2, P - 2));
        end;
      end
        else
      // are we in "our" section?
      if CompareText(InSection, Section) = 0 then
      begin
        P := Pos('=', Line);
        // is it "our" key?
        if (P > 0) and
           (CompareText(Trim(Copy(Line, 1, P - 1)), Key) = 0) then
        begin
          // we are on the line with the Key in Section
          // we will insert new empty line before Key
          SetArrayLength(Lines, GetArrayLength(Lines) + 1);

          for I2 := GetArrayLength(Lines) - 1 downto I + 1 do
          begin
            Lines[I2] := Lines[I2 - 1];
          end;
          // and writing new comment into empty line
          Lines[I] := CommentPrefix + ' ' + Comment;
          Result := SaveStringsToFile(FileName, Lines, False);
          break;
        end;
      end;
    end;
  end;
  
  if not Result then
  begin
    Log('Section/Key not found');
  end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
var
  FileName: string;
begin
  if CurStep = ssPostInstall then
  begin
    FileName := ExpandConstant('{app}\{#AppName}.ini');

    DeleteIniComments(FileName, 'Main', 'OldLogin');
    InsertIniComment(FileName, 'Main', 'OldLogin', '');
    InsertIniComment(FileName, 'Main', 'OldLogin', 'OldLogin=1 will cause a standard ODBC login window to be invoked.');
    InsertIniComment(FileName, 'Main', 'OldLogin', 'This is useful in cases where it is not possible to log in with an internal name and password.');

    DeleteIniComments(FileName, 'Main', 'BufForeground');
    InsertIniComment(FileName, 'Main', 'BufForeground', '');
    InsertIniComment(FileName, 'Main', 'BufForeground', 'Max buffer memory size in MB. Do not use more than 512 MB buffer size!');
    InsertIniComment(FileName, 'Main', 'BufForeground', 'You can set different buffer sizes for foreground and background processes.');

    DeleteIniComments(FileName, 'Devices', 'EluxGenID');
    InsertIniComment(FileName, 'Devices', 'EluxGenID', '');
    InsertIniComment(FileName, 'Devices', 'EluxGenID', 'EluxGenID=1 it finds the device type using a general query.');
    
  end;
end;

This will create INI file that looks like this:

[Main]
; 
; OldLogin=1 will cause a standard ODBC login window to be invoked.
; This is useful in cases where it is not possible to log in with an internal name and password.
OldLogin = 0
; 
; Max buffer memory size in MB. Do not use more than 512 MB buffer size!
; You can set different buffer sizes for foreground and background processes.
BufForeground=512
BufBackground=512
[Devices]
; 
; EluxGenID=1 it finds the device type using a general query.
EluxGenID=0

And you can use it repeatedly (e.g. at every software update). This code will add comments where these does not exist (to new keys) or replace (changed) comments with standard comments.

All keys remain unchanged, where user modified them yet.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992