A lightweight .NET library for reading and writing Windows INI files via the native kernel32 API.
- π About
- β¨ Features
- π Getting Started
- π‘ Usage
- π§ Limitations
- π API Reference
- π§ͺ Running Tests
- π§± Project Structure
- π€ Contributing
- π License
IniFile is a thin, zero-dependency wrapper around the Windows kernel32.dll Private Profile functions (WritePrivateProfileString, GetPrivateProfileString, GetPrivateProfileInt, etc.).
It lets you read and write classic INI configuration files with a simple, strongly-typed C# API β no parsing logic needed. New files are created as UTF-16, so any Unicode text is preserved.
β οΈ Note: This library uses Windows-only P/Invoke calls and is not cross-platform.
| Method | Description |
|---|---|
Write |
βοΈ Write a string value to a key in a given section |
ReadString |
π Read a string value (with optional default) |
ReadInt |
π’ Read an integer value (with optional default) |
ReadBool |
β
Read a boolean β supports true/false, 1/0, yes/no |
GetAllSections |
π List all section names in the file |
GetAllDataSection |
π List all key=value pairs in a section |
DeleteKey |
ποΈ Remove a specific key from a section |
DeleteSection |
π§Ή Remove an entire section and its keys |
KeyExists |
π Check whether a key exists in a section |
- OS: Windows 10 / 11 or Windows Server 2016+
- Target framework:
net10.0or later (.NET 10 SDK)
dotnet add package Laileb.IniFile
Or via the Package Manager Console in Visual Studio:
Install-Package Laileb.IniFile
Or add directly to your .csproj:
<PackageReference Include="Laileb.IniFile" Version="2.1.1" />π‘ No extra project settings are needed: the P/Invoke marshalling code is generated inside the library itself.
using Ini = IniFile.IniFile;
var ini = new Ini("config.ini");
// Write values
ini.Write("Host", "localhost", "Database");
ini.Write("Port", "5432", "Database");
ini.Write("Debug", "true", "Logging");
// Read values
string host = ini.ReadString("Host", "Database"); // "localhost"
int port = ini.ReadInt("Port", "Database"); // 5432
bool dbg = ini.ReadBool("Debug", "Logging"); // true
string miss = ini.ReadString("Missing", "Nope", defaultValue: "N/A"); // "N/A"
// Enumerate
string[] sections = ini.GetAllSections(); // ["Database", "Logging"]
string[] entries = ini.GetAllDataSection("Database"); // ["Host=localhost", "Port=5432"]
// Check & delete
bool exists = ini.KeyExists("Host", "Database"); // true
ini.DeleteKey("Host", "Database");
ini.DeleteSection("Logging");π See the full working demo in examples/IniFile.Example/Program.cs. Run it from the repository root:
dotnet run --project examples/IniFile.ExampleThese come from the underlying Windows API:
-
Encoding. New (missing or empty) files are created as UTF-16 LE with a byte order mark, so any Unicode text (Cyrillic, CJK, emoji) round-trips. This is the only Unicode encoding the Windows profile API supports: it does not understand UTF-8. Files that already have content keep their encoding; in files without the UTF-16 LE byte order mark (ANSI or UTF-8), Windows stores text in the system ANSI code page and replaces other characters with
?, and in a UTF-8 file with a byte order mark it does not recognize the first section. To make such a file Unicode-capable, re-save it as UTF-16 LE with a byte order mark; for a UTF-8 file:File.WriteAllText(path, File.ReadAllText(path), System.Text.Encoding.Unicode). If you need an ANSI file for a legacy tool, create it yourself with some content (for example, a; commentline) before the first write: an empty file is converted to UTF-16. -
Values. On read, Windows trims whitespace around values and removes one pair of surrounding double or single quotes:
"quoted"is read back asquoted. Trailing whitespace ofdefaultValueis trimmed as well. -
Size. Values longer than 32,767 characters are truncated on read. Listing keys, sections, or section entries throws
InvalidOperationExceptionwhen the data does not fit into the 32,768-character buffer. -
Empty section. When
sectionisnull, keys go to the empty section[]. All methods, includingGetAllDataSection(), read it the same way, butGetAllSectionsdoes not list it. -
Errors.
Write,DeleteKey, andDeleteSectionreport I/O failures by returningfalseinstead of throwing (invalid arguments still throwArgumentException). CallMarshal.GetLastPInvokeError()right after a failed call to get the Win32 error code (for example,3when the directory does not exist). -
Case. Windows matches section and key names ignoring ASCII letter case; case-insensitive matching of non-ASCII letters (for example, Cyrillic) is not guaranteed.
-
Threads. Writes from several threads of one process are serialized by the library.
public IniFile(string filePath)Creates a new instance bound to the given file path. The path is resolved to an absolute path internally. Throws ArgumentException if the path is null, empty, or whitespace.
| Property | Type | Description |
|---|---|---|
FilePath |
string |
The fully-qualified path to the INI file. |
public bool Write(string key, string? value, string? section = null)Writes a string value. Returns true on success, false on failure (see Limitations for error codes). Creates the file (as UTF-16 LE, see Limitations), section, and key if they don't exist; the directory must already exist. Passing null as value deletes the key. When section is omitted or null, the wrapper uses an empty section name, which Windows serializes as [].
public string ReadString(string? key, string? section = null, string defaultValue = "", int bufferSize = 1024)Returns the value for the key, or defaultValue if not found (Windows normalizes both, see Limitations). When section is omitted or null, key reads use an empty section name, which Windows serializes as []. Passing null for key preserves the native enumeration behavior: key names for a section, or section names when both key and section are null, returned as one \0-separated string.
bufferSize is the initial buffer (2 to 32768 characters, at least 3 for enumeration); it doubles automatically when a value does not fit.
public int ReadInt(string key, string? section = null, int defaultValue = -1)Returns the integer value for the key, or defaultValue if the key is missing or its value is empty. Parsing follows the native GetPrivateProfileInt: decimal and 0x hexadecimal values are supported, only the leading number is used (12abc β 12), a value that does not start with a number returns 0, and values outside the int range overflow without an error. When section is omitted or null, the wrapper uses an empty section name, which Windows serializes as [].
public bool ReadBool(string key, string? section = null, bool defaultValue = false)Returns true for "true", "1", "yes"; false for "false", "0", "no" (case-insensitive). Returns defaultValue for anything else, including a missing key. When section is omitted or null, the wrapper uses an empty section name, which Windows serializes as [].
public string[] GetAllSections(int bufferSize = 32768)Returns an array of all section names, except the empty [] section (read it with GetAllDataSection()). Returns an empty array if the file has no sections. bufferSize must be from 3 to 32768; InvalidOperationException is thrown when the names do not fit.
public string[] GetAllDataSection(string? section = null, int bufferSize = 32768)Returns an array of "key=value" strings for every entry in the given section, or an empty array if the section does not exist. Comment lines are skipped; lines without = are returned as they are. When section is omitted or null, returns the entries of the empty [] section. bufferSize must be from 3 to 32768; InvalidOperationException is thrown when the entries do not fit.
public bool DeleteKey(string key, string? section = null)Removes a key and its value. Returns true on success, including when the key does not exist. Like all write operations, it creates a missing file (Windows does so even when there is nothing to delete). When section is omitted or null, the wrapper uses an empty section name, which Windows serializes as [].
public bool DeleteSection(string? section = null)Removes an entire section. Returns true on success, including when the section does not exist; a missing file is created, as with DeleteKey. When section is omitted or null, the wrapper deletes the empty [] section.
public bool KeyExists(string key, string? section = null)Returns true if the key exists in the section (including keys with empty values). Keys are matched exactly as ReadString matches them, and the section size is not limited. When section is omitted or null, the wrapper uses an empty section name, which Windows serializes as [].
dotnet testTests call the Windows API, so they run only on Windows (with the .NET 10 SDK). They are located in tests/IniFile.Tests/ and use xUnit. They create temporary INI files in the system temp directory and delete them afterwards.
IniFile/
βββ π .github/workflows/ # CI: build, test, pack, publish to NuGet on tags
βββ π src/
β βββ π IniFile/ # Library source
β βββ π IniFile.cs
β βββ π IniFile.csproj
βββ π tests/
β βββ π IniFile.Tests/ # xUnit tests
β βββ π IniFileTests.cs
β βββ π IniFile.Tests.csproj
βββ π examples/
β βββ π IniFile.Example/ # Console demo app
β βββ π Program.cs
β βββ π IniFile.Example.csproj
βββ π resources/ # NuGet package icon
βββ π Directory.Build.props # Shared build settings
βββ π Directory.Packages.props # Central package management
βββ π IniFile.slnx # Solution file
βββ π LICENSE
βββ π README.md
Contributions are welcome! To get started:
- π΄ Fork the repository
- πΏ Create a feature branch (
git checkout -b feature/my-feature) - βοΈ Make your changes and add tests
- β
Run
dotnet testto verify everything passes - π¬ Open a Pull Request
This project is licensed under the MIT License.