diff --git a/.gitignore b/.gitignore index 38d4953..498050a 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ local.properties ## TODO: If you have NuGet Package Restore enabled, uncomment this packages/ Debug/ +Release/ # Visual C++ cache files ipch/ @@ -116,7 +117,7 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML - +.vs/ ############ ## Windows diff --git a/Paraffin/ArgParser.cs b/Paraffin/ArgParser.cs index accb177..0c4495e 100644 --- a/Paraffin/ArgParser.cs +++ b/Paraffin/ArgParser.cs @@ -7,12 +7,11 @@ // //------------------------------------------------------------------------------ +using System; +using System.Diagnostics; + namespace Wintellect.Paraffin { - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - /// /// A command line argument parsing class. /// @@ -22,9 +21,9 @@ namespace Wintellect.Paraffin /// /// There are two arrays of flags you'll pass to the constructors. The /// flagSymbols are supposed to be standalone switches that toggle an option - /// on. The dataSymbols are for switches that take data values. For - /// example, if your application needs a switch, -c, to set the count, - /// you'd put "c" in the dataSymbols. This code will allow both "-c100" and + /// on. The dataSymbols are for switches that take data values. For + /// example, if your application needs a switch, -c, to set the count, + /// you'd put "c" in the dataSymbols. This code will allow both "-c100" and /// the usual "-c" "100" both to be passed on the command line. Note that /// you can pass null/Nothing for dataSymbols if you don't need them. /// @@ -45,11 +44,11 @@ internal abstract class ArgParser private readonly Boolean caseSensitiveSwitches; /// - /// Initializes a new instance of the ArgParser class and defaults to + /// Initializes a new instance of the ArgParser class and defaults to /// "/" and "-" as the only valid switch characters /// /// - /// The array of simple flags to toggle options on or off. + /// The array of simple flags to toggle options on or off. /// /// /// The array of options that need data either in the next parameter or @@ -58,11 +57,6 @@ internal abstract class ArgParser /// /// True if case sensitive switches are supposed to be used. /// - [SuppressMessage("Microsoft.Naming", - "CA1726:UsePreferredTerms", - MessageId = "flag", - Justification = "Flag is appropriate term when " + - "dealing with command line arguments.")] protected ArgParser(String[] flagSymbols, String[] dataSymbols, Boolean caseSensitiveSwitches) @@ -94,11 +88,6 @@ protected ArgParser(String[] flagSymbols, /// Thrown if or /// are invalid. /// - [SuppressMessage("Microsoft.Naming", - "CA1726:UsePreferredTerms", - MessageId = "flag", - Justification = "Flag is appropriate term when " + - "dealing with command line arguments.")] protected ArgParser(String[] flagSymbols, String[] dataSymbols, Boolean caseSensitiveSwitches, @@ -117,7 +106,7 @@ protected ArgParser(String[] flagSymbols, if ((null == flagSymbols) || (0 == flagSymbols.Length)) { throw new ArgumentException(Constants.ArrayMustBeValid, - "flagSymbols"); + nameof(flagSymbols)); } Debug.Assert(null != switchChars, "null != switchChars"); @@ -133,7 +122,7 @@ protected ArgParser(String[] flagSymbols, if ((null == switchChars) || (0 == switchChars.Length)) { throw new ArgumentException(Constants.ArrayMustBeValid, - "switchChars"); + nameof(switchChars)); } this.flagSymbols = flagSymbols; @@ -178,7 +167,7 @@ protected enum SwitchStatus /// The string array to parse through. /// /// - /// True if parsing was correct. + /// True if parsing was correct. /// /// /// Thrown if is null. diff --git a/Paraffin/Constants.Designer.cs b/Paraffin/Constants.Designer.cs index cc4783e..a4b4567 100644 --- a/Paraffin/Constants.Designer.cs +++ b/Paraffin/Constants.Designer.cs @@ -19,7 +19,7 @@ namespace Wintellect.Paraffin { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Constants { diff --git a/Paraffin/Constants.resx b/Paraffin/Constants.resx index 3afe901..a28e912 100644 --- a/Paraffin/Constants.resx +++ b/Paraffin/Constants.resx @@ -255,6 +255,8 @@ Optional parameters when creating a new fragment: -Permanent - Sets Permanent="Yes" on all file elements. -WiX4 - Create fragments compatible with WiX 4, defaults to WiX 3. + -PerUser - Supports Limited Privileges / Per User MSI by adding + a Registry Value Key Path to each File's Component. Required parameters to update a previously created file for major upgrades. If you delete an input file, using -update will delete the Component and File diff --git a/Paraffin/Main.Create.cs b/Paraffin/Main.Create.cs index 5e6e468..a0e78ad 100644 --- a/Paraffin/Main.Create.cs +++ b/Paraffin/Main.Create.cs @@ -10,6 +10,7 @@ namespace Wintellect.Paraffin { using System; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; @@ -86,7 +87,7 @@ private static void RecurseDirectoriesForNewFile(XElement currElement, // That means I'll be adding files to the node, which // has been passed in the currElement. XElement addToNode = currElement; - if (argValues.NoRootDirectoryState == false) + if (!argValues.NoRootDirectoryState) { // It's new so create a Directory element. XElement directoryNode = CreateDirectoryElement(directory); @@ -109,10 +110,10 @@ private static void RecurseDirectoriesForNewFile(XElement currElement, AddNewFilesToDirectoryNode(directory, addToNode); // Recurse the directories if I'm supposed to do so. - if (false == argValues.NoRecursion) + if (!argValues.NoRecursion) { String[] dirs = Directory.GetDirectories(directory); - foreach (var item in dirs) + foreach (String item in dirs) { Boolean skipDirectory = IsDirectoryExcluded(item); if (false == skipDirectory) @@ -137,11 +138,9 @@ private static void RecurseDirectoriesForNewFile(XElement currElement, private static void AddNewFilesToDirectoryNode(String directory, XElement directoryElem) { - var filesQuery = ProcessedDirectoryFiles(directory); - - // Only do the work if there are some files in the directory. - var files = filesQuery as String[] ?? filesQuery.ToArray(); - if (!files.Any()) + IEnumerable filesQuery = ProcessedDirectoryFiles(directory); + String[] files = filesQuery as String[] ?? filesQuery.ToArray(); + if (files.Length == 0) { return; } @@ -150,12 +149,19 @@ private static void AddNewFilesToDirectoryNode(String directory, XElement currentComponent = CreateComponentElement(); // For each file on disk. - foreach (var file in files) + foreach (String file in files) { // Create the File element and add it to the current // Component element. XElement fileElement = CreateFileElement(file); currentComponent.Add(fileElement); + + if (argValues.PerUser) + { + XElement registryValueElement = CreateRegistryValueElement(file); + currentComponent.Add(registryValueElement); + } + directoryElem.Add(currentComponent); currentComponent = CreateComponentElement(); } diff --git a/Paraffin/Main.Update.cs b/Paraffin/Main.Update.cs index 8e45e7f..440da38 100644 --- a/Paraffin/Main.Update.cs +++ b/Paraffin/Main.Update.cs @@ -363,10 +363,14 @@ private static void UpdateFilesInDirectoryNode(String directory, // file so just add it. First create a new File // element. XElement fileElement = CreateFileElement(file); - - // Add the file to this component. compElement.Add(fileElement); + if (argValues.PerUser) + { + XElement registryValueElement = CreateRegistryValueElement(file); + compElement.Add(registryValueElement); + } + // Add this element to the directory. addToElement.Add(compElement); } @@ -585,7 +589,7 @@ private static void FixKeyPathAttribute(String file, XElement compElement) { var attrib = fileElement.Attribute("KeyPath"); - if (attrib == null) + if (attrib == null && !argValues.PerUser) // When PerUser, KeyPath is stored on a RegistryValue { fileElement.Add( new XAttribute("KeyPath", "yes")); @@ -784,6 +788,19 @@ private static Boolean InitializeArgumentsFromFile(String inputXml) RegexOptions.IgnoreCase)); } + var perUserUsage = options.Descendants(PERUSER); + if (perUserUsage.Count() == 1) + { + String rawValue = perUserUsage.First().Value; + if (0 == String.Compare(rawValue, + "true", + StringComparison.OrdinalIgnoreCase)) + { + argValues.PerUser = true; + } + } + + // Now that everything is read out of the original options block, // add in any additional -ext, -dirExclude, and -regExExclude // options specified on the command line. diff --git a/Paraffin/Main.cs b/Paraffin/Main.cs index 8d6d7c2..06f6098 100644 --- a/Paraffin/Main.cs +++ b/Paraffin/Main.cs @@ -236,6 +236,7 @@ internal static partial class Program private const String REGEXEXITEMELEM = "RegEx"; private const String PERMANENT = "Permanent"; private const String WIX4 = "WiX4"; + private const String PERUSER = "PerUser"; #endregion // The PE file extensions. @@ -465,7 +466,8 @@ private static void AddCommandLineOptionsComment(XElement wixElement) new XElement(NODIRECTORYELEM, argValues.NoRootDirectory), new XElement(DISKIDELEM, argValues.DiskId), new XElement(PERMANENT, argValues.Permanent), - new XElement(WIX4, argValues.WiX4) + new XElement(WIX4, argValues.WiX4), + new XElement(PERUSER, argValues.PerUser) ); // Add the file extension exclusions. @@ -819,13 +821,57 @@ private static XElement CreateFileElement(String fileName) file.Add(new XAttribute("Checksum", "yes")); } - file.Add(new XAttribute("KeyPath", "yes")); + if (!argValues.PerUser) + { + file.Add(new XAttribute("KeyPath", "yes")); + } fileName = AliasedFilename(fileName); file.Add(new XAttribute("Source", fileName)); return file; } + /// + /// Creates a RegistryValue element for the file in . + /// This is to facilitate LimitedPrivilige/PerUser MSI creation + /// + /// + /// The full filename to process. + /// + /// + /// A valid for the RegistryValue element. + /// + private static XElement CreateRegistryValueElement(String fileName) + { + String fileId; + if (argValues.Version == Version1File) + { + // Create a unique filename. In a one file per component run, + // this will mean that the file and it's parent component will + // have the same number. + fileId = CreateSeventyCharIdString("file", + argValues.GroupName, + componentNumber - 1); + } + else + { + Guid g = Guid.NewGuid(); + fileId = String.Format(CultureInfo.InvariantCulture, + "file_{0}", + g.ToString("N").ToUpper(CultureInfo.InvariantCulture)); + } + + XElement registryValue = new XElement(argValues.WixNamespace + "RegistryValue"); + registryValue.Add(new XAttribute("Root", "HKCU")); + registryValue.Add(new XAttribute("Key", "Software\\Tolt Technologies\\Ability Drive\\InstalledFiles")); + registryValue.Add(new XAttribute("Name", fileId)); + registryValue.Add(new XAttribute("Value", "")); + registryValue.Add(new XAttribute("Type", "string")); + registryValue.Add(new XAttribute("KeyPath", "yes")); + + return registryValue; + } + /// /// Creates a standard Component element. /// diff --git a/Paraffin/Paraffin.csproj b/Paraffin/Paraffin.csproj index e6dd058..65b4128 100644 --- a/Paraffin/Paraffin.csproj +++ b/Paraffin/Paraffin.csproj @@ -10,7 +10,7 @@ Properties Wintellect.Paraffin Paraffin - v4.6.2 + v4.8 512 false @@ -52,7 +52,7 @@ true true - ParaffinRuleSet.ruleset + Paraffin.ruleset false @@ -68,7 +68,7 @@ true true - Migrated rules for Paraffin.ruleset + Paraffin.ruleset false @@ -108,7 +108,9 @@ + + diff --git a/Paraffin/Paraffin.ruleset b/Paraffin/Paraffin.ruleset new file mode 100644 index 0000000..2a8beb1 --- /dev/null +++ b/Paraffin/Paraffin.ruleset @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Paraffin/Paraffin.sln b/Paraffin/Paraffin.sln index aa7864a..0b5a9f0 100644 --- a/Paraffin/Paraffin.sln +++ b/Paraffin/Paraffin.sln @@ -1,10 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.32804.182 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paraffin", "Paraffin.csproj", "{69CEBB42-4C4B-4546-9DEE-A5183E989214}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7D9EBD25-EB18-4647-AF98-C4526D711C55}" + ProjectSection(SolutionItems) = preProject + ..\.gitignore = ..\.gitignore + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,4 +24,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F7F8258A-8B29-4FA2-BDA0-5E219B463EC2} + EndGlobalSection EndGlobal diff --git a/Paraffin/ParaffinArgParser.cs b/Paraffin/ParaffinArgParser.cs index 4e8fdaf..b9d0012 100644 --- a/Paraffin/ParaffinArgParser.cs +++ b/Paraffin/ParaffinArgParser.cs @@ -61,6 +61,7 @@ internal class ParaffinArgParser : ArgParser private const String WIN64VAR = "win64var"; private const String PERMANENT = "permanent"; private const String WIX4 = "WiX4"; + private const String PERUSER = "perUser"; #endregion private const String DEFAULTDIRREF = "INSTALLDIR"; @@ -104,7 +105,8 @@ public ParaffinArgParser() VERBOSE, VERBOSESHORT, PERMANENT, - WIX4 + WIX4, + PERUSER }, new[] { @@ -147,6 +149,7 @@ public ParaffinArgParser() this.Win64 = String.Empty; this.Permanent = false; this.WiX4 = false; + this.PerUser = false; this.Version = Program.CurrentFileVersion; @@ -270,6 +273,12 @@ public Boolean WiX4 } } + /// + /// Gets whether the generated file components should include a registry key + /// to support perUser limitedPrivileges MSIs + /// + public Boolean PerUser { get; set; } + /// /// Gets the namespace as this is different between WiX 3 and WiX 4. /// Defaults to WiX3. @@ -560,6 +569,10 @@ protected override SwitchStatus OnSwitch(String switchSymbol, this.WiX4 = true; break; + case PERUSER: + this.PerUser = true; + break; + default: { this.errorMessage = Constants.UnknownCommandLineOption; diff --git a/Paraffin/app.config b/Paraffin/app.config new file mode 100644 index 0000000..3e0e37c --- /dev/null +++ b/Paraffin/app.config @@ -0,0 +1,3 @@ + + + diff --git a/ParaffinInstall/ParaffinInstall.wixproj b/ParaffinInstall/ParaffinInstall.wixproj index e18ed38..aae1937 100644 --- a/ParaffinInstall/ParaffinInstall.wixproj +++ b/ParaffinInstall/ParaffinInstall.wixproj @@ -8,7 +8,7 @@ 2.0 ParaffinInstall Package - $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix2010.targets + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets bin\$(Configuration)\