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