Skip to content

Commit

Permalink
Make sure that SettingFile arg is parsed before we load the settings (P…
Browse files Browse the repository at this point in the history
…owerShell#7449)

Make sure that SettingFile arg is parsed before we load the settings

- Add ability to parse some setting before console host is created
- Trigger the Parse before the console host is created
- disable snapin loading from loading setting before this point
- re-enable mac logging tests.

Also, fix the following Travis-ci issues:

- make sure the Linux and macOS caches are separate to reduce the size
- update the macOS image to the closest image as used by the official build system (equivalent to VSTS hosted mac builds)
  • Loading branch information
TravisEz13 committed Aug 7, 2018
1 parent 0c11582 commit 1523c21
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 139 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ matrix:
- os: linux
dist: trusty
sudo: required
env: TRAVIS_CI_CACHE_NAME=linux
- os: osx
osx_image: xcode8.1
osx_image: xcode9.4
env: TRAVIS_CI_CACHE_NAME=macOS
fast_finish: true

addons:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ internal CommandLineParameterParser(PSHostUserInterface hostUI, string bannerTex
_helpText = helpText;
}

#region Internal properties
internal bool AbortStartup
{
get
Expand Down Expand Up @@ -393,6 +394,205 @@ internal string WorkingDirectory
get { return _workingDirectory; }
}

#endregion Internal properties

#region static methods
/// <summary>
/// Processes the -SettingFile Argument.
/// </summary>
/// <param name="args">
/// The command line parameters to be processed.
/// </param>
/// <param name="settingFileArgIndex">
/// The index in args to the argument following '-SettingFile'.
/// </param>
/// <param name="parser">
/// Used to allow the helper to write errors to the console. If not supplied, no errors will be written.
/// </param>
/// <returns>
/// Returns true if the argument was parsed successfully and false if not.
/// </returns>
private static bool TryParseSettingFileHelper(string[] args, int settingFileArgIndex, CommandLineParameterParser parser)
{
if (settingFileArgIndex >= args.Length)
{
if (parser != null)
{
parser.WriteCommandLineError(
CommandLineParameterParserStrings.MissingSettingsFileArgument);
}

return false;
}

string configFile = null;
try
{
configFile = NormalizeFilePath(args[settingFileArgIndex]);
}
catch (Exception ex)
{
if (parser != null)
{
string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[settingFileArgIndex], ex.Message);
parser.WriteCommandLineError(error);
}

return false;
}

if (!System.IO.File.Exists(configFile))
{
if (parser != null)
{
string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile);
parser.WriteCommandLineError(error);
}

return false;
}
PowerShellConfig.Instance.SetSystemConfigFilePath(configFile);
return true;
}

/// <summary>
/// Processes the command line parameters to ConsoleHost which must be parsed before the Host is created.
/// Success to indicate that the program should continue running.
/// </summary>
/// <param name="args">
/// The command line parameters to be processed.
/// </param>
internal static void EarlyParse(string[] args)
{
// indicates that we've called this method on this instance, and that when it's done, the state variables
// will reflect the parse.

EarlyParseHelper(args);
}

private static string GetConfigurationNameFromGroupPolicy()
{
// Current user policy takes precedence.
var consoleSessionSetting = Utils.GetPolicySetting<ConsoleSessionConfiguration>(Utils.CurrentUserThenSystemWideConfig);

return (consoleSessionSetting?.EnableConsoleSessionConfiguration == true && !string.IsNullOrEmpty(consoleSessionSetting?.ConsoleSessionConfigurationName)) ?
consoleSessionSetting.ConsoleSessionConfigurationName : string.Empty;
}

/// <summary>
/// Processes the command line parameters to ConsoleHost which must be parsed before the Host is created.
/// Success to indicate that the program should continue running.
/// </summary>
/// <param name="args">
/// The command line parameters to be processed.
/// </param>
private static void EarlyParseHelper(string[] args)
{
if(args == null)
{
Dbg.Assert(args != null, "Argument 'args' to EarlyParseHelper should never be null");
return;
}

bool noexitSeen = false;
for (int i = 0; i < args.Length; ++i)
{
(string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, parser: null, ref noexitSeen);
if (switchKeyResults.ShouldBreak)
{
break;
}

string switchKey = switchKeyResults.SwitchKey;

if (MatchSwitch(switchKey, match: "settingsfile", smallestUnambiguousMatch: "settings"))
{
// parse setting file arg and don't write error as there is no host yet.
if (!TryParseSettingFileHelper(args, ++i, parser: null))
{
break;
}
}
}
}

/// <summary>
/// Gets the word in a switch from the current argument or parses a file.
/// For example -foo, /foo, or --foo would return 'foo'.
/// </summary>
/// <param name="args">
/// The command line parameters to be processed.
/// </param>
/// <param name="argIndex">
/// The index in args to the argument to process.
/// </param>
/// <param name="parser">
/// Used to parse files in the args. If not supplied, Files will not be parsed.
/// </param>
/// <param name="noexitSeen">
/// Used during parsing files.
/// </param>
/// <returns>
/// Returns a Tuple:
/// The first value is a String called SwitchKey with the word in a switch from the current argument or null.
/// The second value is a bool called ShouldBreak, indicating if the parsing look should break.
/// </returns>
private static (string SwitchKey, bool ShouldBreak) GetSwitchKey(string[] args, ref int argIndex, CommandLineParameterParser parser, ref bool noexitSeen)
{
string switchKey = args[argIndex].Trim().ToLowerInvariant();
if (string.IsNullOrEmpty(switchKey))
{
return (SwitchKey: null, ShouldBreak: false);
}

if (!SpecialCharacters.IsDash(switchKey[0]) && switchKey[0] != '/')
{
// then its a file
if (parser != null)
{
--argIndex;
parser.ParseFile(args, ref argIndex, noexitSeen);
}

return (SwitchKey: null, ShouldBreak: true);
}

// chop off the first character so that we're agnostic wrt specifying / or -
// in front of the switch name.
switchKey = switchKey.Substring(1);

// chop off the second dash so we're agnostic wrt specifying - or --
if (!string.IsNullOrEmpty(switchKey) && SpecialCharacters.IsDash(switchKey[0]))
{
switchKey = switchKey.Substring(1);
}

return (SwitchKey: switchKey, ShouldBreak: false);
}

private static string NormalizeFilePath(string path)
{
// Normalize slashes
path = path.Replace(StringLiterals.AlternatePathSeparator,
StringLiterals.DefaultPathSeparator);

return Path.GetFullPath(path);
}

private static bool MatchSwitch(string switchKey, string match, string smallestUnambiguousMatch)
{
Dbg.Assert(switchKey != null, "need a value");
Dbg.Assert(!String.IsNullOrEmpty(match), "need a value");
Dbg.Assert(match.Trim().ToLowerInvariant() == match, "match should be normalized to lowercase w/ no outside whitespace");
Dbg.Assert(smallestUnambiguousMatch.Trim().ToLowerInvariant() == smallestUnambiguousMatch, "match should be normalized to lowercase w/ no outside whitespace");
Dbg.Assert(match.Contains(smallestUnambiguousMatch), "sUM should be a substring of match");

return (match.Trim().ToLowerInvariant().IndexOf(switchKey, StringComparison.Ordinal) == 0 &&
switchKey.Length >= smallestUnambiguousMatch.Length);
}

#endregion

private void ShowHelp()
{
Dbg.Assert(_helpText != null, "_helpText should not be null");
Expand Down Expand Up @@ -463,57 +663,20 @@ internal void Parse(string[] args)
}
}

private static string GetConfigurationNameFromGroupPolicy()
{
// Current user policy takes precedence.
var consoleSessionSetting = Utils.GetPolicySetting<ConsoleSessionConfiguration>(Utils.CurrentUserThenSystemWideConfig);
if (consoleSessionSetting != null)
{
if (consoleSessionSetting.EnableConsoleSessionConfiguration == true)
{
if (!string.IsNullOrEmpty(consoleSessionSetting.ConsoleSessionConfigurationName))
{
return consoleSessionSetting.ConsoleSessionConfigurationName;
}
}
}

return string.Empty;
}

private void ParseHelper(string[] args)
{
Dbg.Assert(args != null, "Argument 'args' to ParseHelper should never be null");
bool noexitSeen = false;

for (int i = 0; i < args.Length; ++i)
{
// Invariant culture used because command-line parameters are not localized.

string switchKey = args[i].Trim().ToLowerInvariant();
if (String.IsNullOrEmpty(switchKey))
(string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, this, ref noexitSeen);
if (switchKeyResults.ShouldBreak)
{
continue;
}

if (!SpecialCharacters.IsDash(switchKey[0]) && switchKey[0] != '/')
{
// then its a file

--i;
ParseFile(args, ref i, noexitSeen);
break;
}

// chop off the first character so that we're agnostic wrt specifying / or -
// in front of the switch name.
switchKey = switchKey.Substring(1);

// chop off the second dash so we're agnostic wrt specifying - or --
if (!String.IsNullOrEmpty(switchKey) && SpecialCharacters.IsDash(switchKey[0]))
{
switchKey = switchKey.Substring(1);
}
string switchKey = switchKeyResults.SwitchKey;

// If version is in the commandline, don't continue to look at any other parameters
if (MatchSwitch(switchKey, "version", "v"))
Expand Down Expand Up @@ -711,34 +874,13 @@ private void ParseHelper(string[] args)
}
}

else if (MatchSwitch(switchKey, "settingsfile", "settings") )
else if (MatchSwitch(switchKey, "settingsfile", "settings"))
{
++i;
if (i >= args.Length)
// Parse setting file arg and write error
if (!TryParseSettingFileHelper(args, ++i, this))
{
WriteCommandLineError(
CommandLineParameterParserStrings.MissingSettingsFileArgument);
break;
}
string configFile = null;
try
{
configFile = NormalizeFilePath(args[i]);
}
catch (Exception ex)
{
string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[i], ex.Message);
WriteCommandLineError(error);
break;
}

if (!System.IO.File.Exists(configFile))
{
string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile);
WriteCommandLineError(error);
break;
}
PowerShellConfig.Instance.SetSystemConfigFilePath(configFile);
}
#if STAMODE
// explicit setting of the ApartmentState Not supported on NanoServer
Expand Down Expand Up @@ -818,25 +960,6 @@ private void WriteCommandLineError(string msg, bool showHelp = false, bool showB
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
}

private bool MatchSwitch(string switchKey, string match, string smallestUnambiguousMatch)
{
Dbg.Assert(switchKey != null, "need a value");
Dbg.Assert(!String.IsNullOrEmpty(match), "need a value");
Dbg.Assert(match.Trim().ToLowerInvariant() == match, "match should be normalized to lowercase w/ no outside whitespace");
Dbg.Assert(smallestUnambiguousMatch.Trim().ToLowerInvariant() == smallestUnambiguousMatch, "match should be normalized to lowercase w/ no outside whitespace");
Dbg.Assert(match.Contains(smallestUnambiguousMatch), "sUM should be a substring of match");

if (match.Trim().ToLowerInvariant().IndexOf(switchKey, StringComparison.Ordinal) == 0)
{
if (switchKey.Length >= smallestUnambiguousMatch.Length)
{
return true;
}
}

return false;
}

private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat format, string resourceStr)
{
StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -892,15 +1015,6 @@ private void ParseExecutionPolicy(string[] args, ref int i, ref string execution
executionPolicy = args[i];
}

private static string NormalizeFilePath(string path)
{
// Normalize slashes
path = path.Replace(StringLiterals.AlternatePathSeparator,
StringLiterals.DefaultPathSeparator);

return Path.GetFullPath(path);
}

private bool ParseFile(string[] args, ref int i, bool noexitSeen)
{
// Process file execution. We don't need to worry about checking -command
Expand Down
8 changes: 6 additions & 2 deletions src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ internal static int Start(

try
{
// We need to read the settings file before we create the console host
string[] tempArgs = new string[args.GetLength(0)];
args.CopyTo(tempArgs, 0);

CommandLineParameterParser.EarlyParse(tempArgs);

// We might be able to ignore console host creation error if we are running in
// server mode, which does not require a console.
HostException hostException = null;
Expand All @@ -168,8 +174,6 @@ internal static int Start(
s_cpp = new CommandLineParameterParser(
(s_theConsoleHost != null) ? s_theConsoleHost.UI : (new NullHostUserInterface()),
bannerText, helpText);
string[] tempArgs = new string[args.GetLength(0)];
args.CopyTo(tempArgs, 0);

s_cpp.Parse(tempArgs);

Expand Down
Loading

0 comments on commit 1523c21

Please sign in to comment.