diff --git a/src/Microsoft.VisualStudio.SlowCheetah/Transformer/IniTransformer.cs b/src/Microsoft.VisualStudio.SlowCheetah/Transformer/IniTransformer.cs
new file mode 100644
index 00000000..e09f5530
--- /dev/null
+++ b/src/Microsoft.VisualStudio.SlowCheetah/Transformer/IniTransformer.cs
@@ -0,0 +1,205 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.SlowCheetah
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using Microsoft.VisualStudio.Jdt;
+
+ ///
+ /// Transforms Ini files
+ ///
+ public class IniTransformer : ITransformer
+ {
+ private IJsonTransformationLogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IniTransformer()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with an external logger
+ ///
+ /// The external logger
+ public IniTransformer(ITransformationLogger logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ this.logger = new JsonShimLogger(logger);
+ }
+
+ ///
+ public void CreateTransformFile(string sourcePath, string transformPath, bool overwrite)
+ {
+ if (string.IsNullOrWhiteSpace(sourcePath))
+ {
+ throw new ArgumentNullException(nameof(sourcePath));
+ }
+
+ if (string.IsNullOrWhiteSpace(transformPath))
+ {
+ throw new ArgumentNullException(nameof(transformPath));
+ }
+
+ if (!File.Exists(sourcePath))
+ {
+ throw new FileNotFoundException(Resources.Resources.ErrorMessage_SourceFileNotFound, sourcePath);
+ }
+
+ // If the file should be overwritten or if it doesn't exist, we create it
+ if (overwrite || !File.Exists(transformPath))
+ {
+ var encoding = TransformUtilities.GetEncoding(sourcePath);
+ File.WriteAllText(transformPath, string.Empty, encoding);
+ }
+ }
+
+ ///
+ public bool IsFileSupported(string filePath)
+ {
+ if (string.IsNullOrWhiteSpace(filePath))
+ {
+ throw new ArgumentNullException(nameof(filePath));
+ }
+
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException(Resources.Resources.ErrorMessage_FileNotFound, filePath);
+ }
+
+ return Path.GetExtension(filePath).Equals(".ini", StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ public bool Transform(string sourcePath, string transformPath, string destinationPath)
+ {
+ if (string.IsNullOrWhiteSpace(sourcePath))
+ {
+ throw new ArgumentException($"{nameof(sourcePath)} cannot be null or whitespace");
+ }
+
+ if (string.IsNullOrWhiteSpace(transformPath))
+ {
+ throw new ArgumentException($"{nameof(transformPath)} cannot be null or whitespace");
+ }
+
+ if (string.IsNullOrWhiteSpace(destinationPath))
+ {
+ throw new ArgumentException($"{nameof(destinationPath)} cannot be null or whitespace");
+ }
+
+ if (!File.Exists(sourcePath))
+ {
+ throw new FileNotFoundException(Resources.Resources.ErrorMessage_SourceFileNotFound, sourcePath);
+ }
+
+ if (!File.Exists(transformPath))
+ {
+ throw new FileNotFoundException(Resources.Resources.ErrorMessage_TransformFileNotFound, transformPath);
+ }
+
+ try
+ {
+ Dictionary transformDictionary = new Dictionary();
+ string currentRoot = null;
+ string[] keyPair = null;
+
+ if (!System.IO.File.Exists(sourcePath) || !System.IO.File.Exists(transformPath))
+ {
+ return false;
+ }
+
+ // Collect what we need to replace
+ using (StreamReader iniFile = new StreamReader(transformPath))
+ {
+ string strLine = iniFile.ReadLine();
+ while (strLine != null)
+ {
+ if (!string.IsNullOrEmpty(strLine))
+ {
+ if (strLine.StartsWith("[") && strLine.EndsWith("]"))
+ {
+ currentRoot = strLine.Substring(1, strLine.Length - 2);
+ }
+ else if (!strLine.StartsWith(";") && !strLine.StartsWith("#"))
+ {
+ keyPair = strLine.Split(new char[] { '=' }, 2);
+ if (keyPair.Length > 1 && !transformDictionary.ContainsKey(currentRoot + "-" + keyPair[0]))
+ {
+ transformDictionary.Add(currentRoot + "-" + keyPair[0], keyPair[1]);
+ }
+ }
+ }
+ strLine = iniFile.ReadLine();
+ }
+ }
+
+ // read the source file and replace lines where is necessary
+ using (StreamReader iniFile = new StreamReader(sourcePath))
+ {
+ using (StreamWriter destFile = new StreamWriter(destinationPath))
+ {
+ string strLine = iniFile.ReadLine();
+ while (strLine != null)
+ {
+ if (!string.IsNullOrEmpty(strLine))
+ {
+ if (strLine.StartsWith("[") && strLine.EndsWith("]"))
+ {
+ currentRoot = strLine.Substring(1, strLine.Length - 2);
+ }
+ else if (!strLine.StartsWith(";") && !strLine.StartsWith("#"))
+ {
+ keyPair = strLine.Split(new char[] { '=' }, 2);
+ if (keyPair.Length > 1 && transformDictionary.ContainsKey(currentRoot + "-" + keyPair[0]))
+ {
+ strLine = keyPair[0] + "=" + transformDictionary[currentRoot + "-" + keyPair[0]];
+ }
+ }
+ destFile.WriteLine(strLine);
+ }
+ else
+ {
+ destFile.WriteLine(strLine);
+ }
+
+ strLine = iniFile.ReadLine();
+ }
+ }
+ }
+
+ return true;
+ }
+ catch
+ {
+ // JDT exceptions are handled by it's own logger
+ return false;
+ }
+ }
+
+ ///
+ public ITransformer WithLogger(ITransformationLogger logger)
+ {
+ if (logger == this.logger)
+ {
+ return this;
+ }
+ else if (logger == null)
+ {
+ return new IniTransformer();
+ }
+ else
+ {
+ return new IniTransformer(logger);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.SlowCheetah/Transformer/TransformerFactory.cs b/src/Microsoft.VisualStudio.SlowCheetah/Transformer/TransformerFactory.cs
index 2d7c506c..15937683 100644
--- a/src/Microsoft.VisualStudio.SlowCheetah/Transformer/TransformerFactory.cs
+++ b/src/Microsoft.VisualStudio.SlowCheetah/Transformer/TransformerFactory.cs
@@ -18,6 +18,7 @@ public static class TransformerFactory
{
new JsonTransformer(),
new XmlTransformer(),
+ new IniTransformer()
};
///