mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 12:45:47 +00:00
1454 lines
50 KiB
C#
1454 lines
50 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Configuration;
|
|
using System.Management.Automation.Host;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace Microsoft.PowerShell
|
|
{
|
|
/// <summary>
|
|
/// Null class implementation of PSHostUserInterface used when no console is available and when PowerShell
|
|
/// is run in servmode where no console is needed.
|
|
/// </summary>
|
|
internal class NullHostUserInterface : PSHostUserInterface
|
|
{
|
|
/// <summary>
|
|
/// RawUI.
|
|
/// </summary>
|
|
public override PSHostRawUserInterface RawUI
|
|
{
|
|
get { return null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prompt.
|
|
/// </summary>
|
|
/// <param name="caption"></param>
|
|
/// <param name="message"></param>
|
|
/// <param name="descriptions"></param>
|
|
/// <returns></returns>
|
|
public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions)
|
|
{
|
|
throw new PSNotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// PromptForChoice.
|
|
/// </summary>
|
|
/// <param name="caption"></param>
|
|
/// <param name="message"></param>
|
|
/// <param name="choices"></param>
|
|
/// <param name="defaultChoice"></param>
|
|
/// <returns></returns>
|
|
public override int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice)
|
|
{
|
|
throw new PSNotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// PromptForCredential.
|
|
/// </summary>
|
|
/// <param name="caption"></param>
|
|
/// <param name="message"></param>
|
|
/// <param name="userName"></param>
|
|
/// <param name="targetName"></param>
|
|
/// <returns></returns>
|
|
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
|
|
{
|
|
throw new PSNotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// PromptForCredential.
|
|
/// </summary>
|
|
/// <param name="caption"></param>
|
|
/// <param name="message"></param>
|
|
/// <param name="userName"></param>
|
|
/// <param name="targetName"></param>
|
|
/// <param name="allowedCredentialTypes"></param>
|
|
/// <param name="options"></param>
|
|
/// <returns></returns>
|
|
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
|
|
{
|
|
throw new PSNotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// ReadLine.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ReadLine()
|
|
{
|
|
throw new PSNotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// ReadLineAsSecureString.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override SecureString ReadLineAsSecureString()
|
|
{
|
|
throw new PSNotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
public override void Write(string value)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Write.
|
|
/// </summary>
|
|
/// <param name="foregroundColor"></param>
|
|
/// <param name="backgroundColor"></param>
|
|
/// <param name="value"></param>
|
|
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// WriteDebugLine.
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
public override void WriteDebugLine(string message)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// WriteErrorLine.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
public override void WriteErrorLine(string value)
|
|
{
|
|
Console.Out.WriteLine(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// WriteLine.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
public override void WriteLine(string value)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// WriteProgress.
|
|
/// </summary>
|
|
/// <param name="sourceId"></param>
|
|
/// <param name="record"></param>
|
|
public override void WriteProgress(long sourceId, ProgressRecord record)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// WriteVerboseLine.
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
public override void WriteVerboseLine(string message)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// WriteWarningLine.
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
public override void WriteWarningLine(string message)
|
|
{ }
|
|
}
|
|
|
|
internal class CommandLineParameterParser
|
|
{
|
|
private const int MaxPipePathLengthLinux = 108;
|
|
private const int MaxPipePathLengthMacOS = 104;
|
|
|
|
internal static string[] validParameters = {
|
|
#if STAMODE
|
|
"sta",
|
|
"mta",
|
|
#endif
|
|
"command",
|
|
"configurationname",
|
|
"custompipename",
|
|
"encodedcommand",
|
|
"executionpolicy",
|
|
"file",
|
|
"help",
|
|
"inputformat",
|
|
"loadprofile",
|
|
"noexit",
|
|
"nologo",
|
|
"noninteractive",
|
|
"noprofile",
|
|
"outputformat",
|
|
"removeworkingdirectorytrailingcharacter",
|
|
"settingsfile",
|
|
"version",
|
|
"windowstyle",
|
|
"workingdirectory"
|
|
};
|
|
|
|
internal CommandLineParameterParser(PSHostUserInterface hostUI, string bannerText, string helpText)
|
|
{
|
|
if (hostUI == null) { throw new PSArgumentNullException("hostUI"); }
|
|
|
|
_hostUI = hostUI;
|
|
|
|
_bannerText = bannerText;
|
|
_helpText = helpText;
|
|
}
|
|
|
|
#region Internal properties
|
|
internal bool AbortStartup
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _abortStartup;
|
|
}
|
|
}
|
|
|
|
internal string InitialCommand
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _commandLineCommand;
|
|
}
|
|
}
|
|
|
|
internal bool WasInitialCommandEncoded
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _wasCommandEncoded;
|
|
}
|
|
}
|
|
|
|
internal bool ShowBanner
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
return _showBanner;
|
|
}
|
|
}
|
|
|
|
internal bool NoExit
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _noExit;
|
|
}
|
|
}
|
|
|
|
internal bool SkipProfiles
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _skipUserInit;
|
|
}
|
|
}
|
|
|
|
internal uint ExitCode
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _exitCode;
|
|
}
|
|
}
|
|
|
|
internal bool ExplicitReadCommandsFromStdin
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _explicitReadCommandsFromStdin;
|
|
}
|
|
}
|
|
|
|
internal bool NoPrompt
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _noPrompt;
|
|
}
|
|
}
|
|
|
|
internal Collection<CommandParameter> Args
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _collectedArgs;
|
|
}
|
|
}
|
|
|
|
internal string ConfigurationName
|
|
{
|
|
get { return _configurationName; }
|
|
}
|
|
|
|
internal bool SocketServerMode
|
|
{
|
|
get
|
|
{
|
|
return _socketServerMode;
|
|
}
|
|
}
|
|
|
|
internal bool NamedPipeServerMode
|
|
{
|
|
get { return _namedPipeServerMode; }
|
|
}
|
|
|
|
internal bool SSHServerMode
|
|
{
|
|
get { return _sshServerMode; }
|
|
}
|
|
|
|
internal bool ServerMode
|
|
{
|
|
get
|
|
{
|
|
return _serverMode;
|
|
}
|
|
}
|
|
|
|
internal bool ShowVersion
|
|
{
|
|
get
|
|
{
|
|
return _showVersion;
|
|
}
|
|
}
|
|
|
|
internal string CustomPipeName
|
|
{
|
|
get
|
|
{
|
|
return _customPipeName;
|
|
}
|
|
}
|
|
|
|
internal Serialization.DataFormat OutputFormat
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _outFormat;
|
|
}
|
|
}
|
|
|
|
internal bool OutputFormatSpecified
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
|
|
return _outputFormatSpecified;
|
|
}
|
|
}
|
|
|
|
internal Serialization.DataFormat InputFormat
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
return _inFormat;
|
|
}
|
|
}
|
|
|
|
internal string File
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
return _file;
|
|
}
|
|
}
|
|
|
|
internal string ExecutionPolicy
|
|
{
|
|
get
|
|
{
|
|
Dbg.Assert(_dirty, "Parse has not been called yet");
|
|
return _executionPolicy;
|
|
}
|
|
}
|
|
|
|
internal bool ThrowOnReadAndPrompt
|
|
{
|
|
get
|
|
{
|
|
return _noInteractive;
|
|
}
|
|
}
|
|
|
|
internal bool NonInteractive
|
|
{
|
|
get { return _noInteractive; }
|
|
}
|
|
|
|
internal string WorkingDirectory
|
|
{
|
|
get
|
|
{
|
|
#if !UNIX
|
|
if (_removeWorkingDirectoryTrailingCharacter && _workingDirectory.Length > 0)
|
|
{
|
|
return _workingDirectory.Remove(_workingDirectory.Length - 1);
|
|
}
|
|
#endif
|
|
return _workingDirectory;
|
|
}
|
|
}
|
|
|
|
#if !UNIX
|
|
internal bool RemoveWorkingDirectoryTrailingCharacter
|
|
{
|
|
get { return _removeWorkingDirectoryTrailingCharacter; }
|
|
}
|
|
#endif
|
|
|
|
#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");
|
|
_hostUI.WriteLine(string.Empty);
|
|
_hostUI.Write(_helpText);
|
|
if (_showExtendedHelp)
|
|
{
|
|
_hostUI.Write(ManagedEntranceStrings.ExtendedHelp);
|
|
}
|
|
|
|
_hostUI.WriteLine(string.Empty);
|
|
}
|
|
|
|
private void DisplayBanner()
|
|
{
|
|
// If banner text is not supplied do nothing.
|
|
if (!string.IsNullOrEmpty(_bannerText))
|
|
{
|
|
_hostUI.WriteLine(_bannerText);
|
|
_hostUI.WriteLine();
|
|
}
|
|
}
|
|
|
|
#if STAMODE
|
|
internal bool StaMode
|
|
{
|
|
get
|
|
{
|
|
if (_staMode.HasValue)
|
|
{
|
|
return _staMode.Value;
|
|
}
|
|
else
|
|
{
|
|
// Nano doesn't support STA COM apartment, so on Nano powershell has to use MTA as the default.
|
|
// return false;
|
|
// Win8: 182409 PowerShell 3.0 should run in STA mode by default
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Processes all the command line parameters to ConsoleHost. Returns the exit code to be used to terminate the process, or
|
|
/// Success to indicate that the program should continue running.
|
|
/// </summary>
|
|
/// <param name="args">
|
|
/// The command line parameters to be processed.
|
|
/// </param>
|
|
|
|
internal void Parse(string[] args)
|
|
{
|
|
Dbg.Assert(!_dirty, "This instance has already been used. Create a new instance.");
|
|
|
|
// indicates that we've called this method on this instance, and that when it's done, the state variables
|
|
// will reflect the parse.
|
|
|
|
_dirty = true;
|
|
|
|
ParseHelper(args);
|
|
|
|
// Check registry setting for a Group Policy ConfigurationName entry and
|
|
// use it to override anything set by the user.
|
|
var configurationName = GetConfigurationNameFromGroupPolicy();
|
|
if (!string.IsNullOrEmpty(configurationName))
|
|
{
|
|
_configurationName = configurationName;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
(string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, this, ref noexitSeen);
|
|
if (switchKeyResults.ShouldBreak)
|
|
{
|
|
break;
|
|
}
|
|
|
|
string switchKey = switchKeyResults.SwitchKey;
|
|
|
|
// If version is in the commandline, don't continue to look at any other parameters
|
|
if (MatchSwitch(switchKey, "version", "v"))
|
|
{
|
|
_showVersion = true;
|
|
_showBanner = false;
|
|
_noInteractive = true;
|
|
_skipUserInit = true;
|
|
_noExit = false;
|
|
break;
|
|
}
|
|
else if (MatchSwitch(switchKey, "help", "h") || MatchSwitch(switchKey, "?", "?"))
|
|
{
|
|
_showHelp = true;
|
|
_showExtendedHelp = true;
|
|
_abortStartup = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "noexit", "noe"))
|
|
{
|
|
_noExit = true;
|
|
noexitSeen = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "loadprofile", "l"))
|
|
{
|
|
_skipUserInit = false;
|
|
}
|
|
else if (MatchSwitch(switchKey, "noprofile", "nop"))
|
|
{
|
|
_skipUserInit = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "nologo", "nol"))
|
|
{
|
|
_showBanner = false;
|
|
}
|
|
else if (MatchSwitch(switchKey, "noninteractive", "noni"))
|
|
{
|
|
_noInteractive = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "socketservermode", "so"))
|
|
{
|
|
_socketServerMode = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "servermode", "s"))
|
|
{
|
|
_serverMode = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "namedpipeservermode", "nam"))
|
|
{
|
|
_namedPipeServerMode = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "sshservermode", "sshs"))
|
|
{
|
|
_sshServerMode = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "interactive", "i"))
|
|
{
|
|
_noInteractive = false;
|
|
}
|
|
else if (MatchSwitch(switchKey, "configurationname", "config"))
|
|
{
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MissingConfigurationNameArgument);
|
|
break;
|
|
}
|
|
|
|
_configurationName = args[i];
|
|
}
|
|
else if (MatchSwitch(switchKey, "custompipename", "cus"))
|
|
{
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MissingCustomPipeNameArgument);
|
|
break;
|
|
}
|
|
|
|
if (!Platform.IsWindows)
|
|
{
|
|
int maxNameLength = (Platform.IsLinux ? MaxPipePathLengthLinux : MaxPipePathLengthMacOS) - Path.GetTempPath().Length;
|
|
if (args[i].Length > maxNameLength)
|
|
{
|
|
WriteCommandLineError(
|
|
string.Format(
|
|
CommandLineParameterParserStrings.CustomPipeNameTooLong,
|
|
maxNameLength,
|
|
args[i],
|
|
args[i].Length));
|
|
break;
|
|
}
|
|
}
|
|
|
|
_customPipeName = args[i];
|
|
}
|
|
else if (MatchSwitch(switchKey, "command", "c"))
|
|
{
|
|
if (!ParseCommand(args, ref i, noexitSeen, false))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (MatchSwitch(switchKey, "windowstyle", "w"))
|
|
{
|
|
#if UNIX
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.WindowStyleArgumentNotImplemented);
|
|
break;
|
|
#else
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MissingWindowStyleArgument);
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
ProcessWindowStyle style = (ProcessWindowStyle)LanguagePrimitives.ConvertTo(
|
|
args[i], typeof(ProcessWindowStyle), CultureInfo.InvariantCulture);
|
|
ConsoleControl.SetConsoleMode(style);
|
|
}
|
|
catch (PSInvalidCastException e)
|
|
{
|
|
WriteCommandLineError(
|
|
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidWindowStyleArgument, args[i], e.Message));
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
else if (MatchSwitch(switchKey, "file", "f"))
|
|
{
|
|
if (!ParseFile(args, ref i, noexitSeen))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
#if DEBUG
|
|
// this option is useful when debugging ConsoleHost remotely using VS remote debugging, as you can only
|
|
// attach to an already running process with that debugger.
|
|
else if (MatchSwitch(switchKey, "wait", "w"))
|
|
{
|
|
// This does not need to be localized: its chk only
|
|
|
|
((ConsoleHostUserInterface)_hostUI).WriteToConsole("Waiting - type enter to continue:", false);
|
|
_hostUI.ReadLine();
|
|
}
|
|
|
|
// this option is useful for testing the initial InitialSessionState experience
|
|
else if (MatchSwitch(switchKey, "iss", "iss"))
|
|
{
|
|
// Just toss this option, it was processed earlier...
|
|
}
|
|
|
|
// this option is useful for testing the initial InitialSessionState experience
|
|
// this is independent of the normal wait switch because configuration processing
|
|
// happens so early in the cycle...
|
|
else if (MatchSwitch(switchKey, "isswait", "isswait"))
|
|
{
|
|
// Just toss this option, it was processed earlier...
|
|
}
|
|
|
|
else if (MatchSwitch(switchKey, "modules", "mod"))
|
|
{
|
|
if (ConsoleHost.DefaultInitialSessionState == null)
|
|
{
|
|
WriteCommandLineError(
|
|
"The -module option can only be specified with the -iss option.",
|
|
showHelp: true,
|
|
showBanner: false);
|
|
break;
|
|
}
|
|
|
|
++i;
|
|
int moduleCount = 0;
|
|
// Accumulate the arguments to this script...
|
|
while (i < args.Length)
|
|
{
|
|
string arg = args[i];
|
|
|
|
if (!string.IsNullOrEmpty(arg) && SpecialCharacters.IsDash(arg[0]))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ConsoleHost.DefaultInitialSessionState.ImportPSModule(new string[] { arg });
|
|
moduleCount++;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
if (moduleCount < 1)
|
|
{
|
|
_hostUI.WriteErrorLine("No modules specified for -module option");
|
|
}
|
|
}
|
|
#endif
|
|
else if (MatchSwitch(switchKey, "outputformat", "o") || MatchSwitch(switchKey, "of", "o"))
|
|
{
|
|
ParseFormat(args, ref i, ref _outFormat, CommandLineParameterParserStrings.MissingOutputFormatParameter);
|
|
_outputFormatSpecified = true;
|
|
}
|
|
else if (MatchSwitch(switchKey, "inputformat", "in") || MatchSwitch(switchKey, "if", "if"))
|
|
{
|
|
ParseFormat(args, ref i, ref _inFormat, CommandLineParameterParserStrings.MissingInputFormatParameter);
|
|
}
|
|
else if (MatchSwitch(switchKey, "executionpolicy", "ex") || MatchSwitch(switchKey, "ep", "ep"))
|
|
{
|
|
ParseExecutionPolicy(args, ref i, ref _executionPolicy, CommandLineParameterParserStrings.MissingExecutionPolicyParameter);
|
|
}
|
|
else if (MatchSwitch(switchKey, "encodedcommand", "e") || MatchSwitch(switchKey, "ec", "e"))
|
|
{
|
|
_wasCommandEncoded = true;
|
|
if (!ParseCommand(args, ref i, noexitSeen, true))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (MatchSwitch(switchKey, "encodedarguments", "encodeda") || MatchSwitch(switchKey, "ea", "ea"))
|
|
{
|
|
if (!CollectArgs(args, ref i))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (MatchSwitch(switchKey, "settingsfile", "settings"))
|
|
{
|
|
// Parse setting file arg and write error
|
|
if (!TryParseSettingFileHelper(args, ++i, this))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
#if STAMODE
|
|
// explicit setting of the ApartmentState Not supported on NanoServer
|
|
else if (MatchSwitch(switchKey, "sta", "s"))
|
|
{
|
|
if (_staMode.HasValue)
|
|
{
|
|
// -sta and -mta are mutually exclusive.
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MtaStaMutuallyExclusive);
|
|
break;
|
|
}
|
|
|
|
_staMode = true;
|
|
}
|
|
// Win8: 182409 PowerShell 3.0 should run in STA mode by default..so, consequently adding the switch -mta.
|
|
// Not deleting -sta for backward compatability reasons
|
|
else if (MatchSwitch(switchKey, "mta", "mta"))
|
|
{
|
|
if (_staMode.HasValue)
|
|
{
|
|
// -sta and -mta are mutually exclusive.
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MtaStaMutuallyExclusive);
|
|
break;
|
|
}
|
|
|
|
_staMode = false;
|
|
}
|
|
#endif
|
|
else if (MatchSwitch(switchKey, "workingdirectory", "wo") || MatchSwitch(switchKey, "wd", "wd"))
|
|
{
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MissingWorkingDirectoryArgument);
|
|
break;
|
|
}
|
|
|
|
_workingDirectory = args[i];
|
|
}
|
|
#if !UNIX
|
|
else if (MatchSwitch(switchKey, "removeworkingdirectorytrailingcharacter", "removeworkingdirectorytrailingcharacter"))
|
|
{
|
|
_removeWorkingDirectoryTrailingCharacter = true;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
// The first parameter we fail to recognize marks the beginning of the file string.
|
|
|
|
--i;
|
|
if (!ParseFile(args, ref i, noexitSeen))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_showHelp)
|
|
{
|
|
ShowHelp();
|
|
}
|
|
|
|
if (_showBanner && !_showHelp)
|
|
{
|
|
DisplayBanner();
|
|
}
|
|
|
|
Dbg.Assert(
|
|
((_exitCode == ConsoleHost.ExitCodeBadCommandLineParameter) && _abortStartup)
|
|
|| (_exitCode == ConsoleHost.ExitCodeSuccess),
|
|
"if exit code is failure, then abortstartup should be true");
|
|
}
|
|
|
|
private void WriteCommandLineError(string msg, bool showHelp = false, bool showBanner = false)
|
|
{
|
|
_hostUI.WriteErrorLine(msg);
|
|
_showHelp = showHelp;
|
|
_showBanner = showBanner;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
}
|
|
|
|
private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat format, string resourceStr)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
foreach (string s in Enum.GetNames(typeof(Serialization.DataFormat)))
|
|
{
|
|
sb.Append(s);
|
|
sb.Append(Environment.NewLine);
|
|
}
|
|
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
_hostUI.WriteErrorLine(
|
|
StringUtil.Format(
|
|
resourceStr,
|
|
sb.ToString()));
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
format = (Serialization.DataFormat)Enum.Parse(typeof(Serialization.DataFormat), args[i], true);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
_hostUI.WriteErrorLine(
|
|
StringUtil.Format(
|
|
CommandLineParameterParserStrings.BadFormatParameterValue,
|
|
args[i],
|
|
sb.ToString()));
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
}
|
|
}
|
|
|
|
private void ParseExecutionPolicy(string[] args, ref int i, ref string executionPolicy, string resourceStr)
|
|
{
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
_hostUI.WriteErrorLine(resourceStr);
|
|
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return;
|
|
}
|
|
|
|
executionPolicy = args[i];
|
|
}
|
|
|
|
private bool ParseFile(string[] args, ref int i, bool noexitSeen)
|
|
{
|
|
// Process file execution. We don't need to worry about checking -command
|
|
// since if -command comes before -file, -file will be treated as part
|
|
// of the script to evaluate. If -file comes before -command, it will
|
|
// treat -command as an argument to the script...
|
|
|
|
bool TryGetBoolValue(string arg, out bool boolValue)
|
|
{
|
|
if (arg.Equals("$true", StringComparison.OrdinalIgnoreCase) || arg.Equals("true", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
boolValue = true;
|
|
return true;
|
|
}
|
|
else if (arg.Equals("$false", StringComparison.OrdinalIgnoreCase) || arg.Equals("false", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
boolValue = false;
|
|
return true;
|
|
}
|
|
|
|
boolValue = false;
|
|
return false;
|
|
}
|
|
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
WriteCommandLineError(
|
|
CommandLineParameterParserStrings.MissingFileArgument,
|
|
showHelp: true,
|
|
showBanner: false);
|
|
return false;
|
|
}
|
|
|
|
// Don't show the startup banner unless -noexit has been specified.
|
|
if (!noexitSeen)
|
|
_showBanner = false;
|
|
|
|
// Process interactive input...
|
|
if (args[i] == "-")
|
|
{
|
|
// the arg to -file is -, which is secret code for "read the commands from stdin with prompts"
|
|
|
|
_explicitReadCommandsFromStdin = true;
|
|
_noPrompt = false;
|
|
}
|
|
else
|
|
{
|
|
// Exit on script completion unless -noexit was specified...
|
|
if (!noexitSeen)
|
|
_noExit = false;
|
|
|
|
// We need to get the full path to the script because it will be
|
|
// executed after the profiles are run and they may change the current
|
|
// directory.
|
|
string exceptionMessage = null;
|
|
try
|
|
{
|
|
_file = NormalizeFilePath(args[i]);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Catch all exceptions - we're just going to exit anyway so there's
|
|
// no issue of the system being destabilized.
|
|
exceptionMessage = e.Message;
|
|
}
|
|
|
|
if (exceptionMessage != null)
|
|
{
|
|
WriteCommandLineError(
|
|
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgument, args[i], exceptionMessage),
|
|
showBanner: false);
|
|
return false;
|
|
}
|
|
|
|
if (!System.IO.File.Exists(_file))
|
|
{
|
|
if (args[i].StartsWith("-") && args[i].Length > 1)
|
|
{
|
|
string param = args[i].Substring(1, args[i].Length - 1).ToLower();
|
|
StringBuilder possibleParameters = new StringBuilder();
|
|
foreach (string validParameter in validParameters)
|
|
{
|
|
if (validParameter.Contains(param))
|
|
{
|
|
possibleParameters.Append("\n -");
|
|
possibleParameters.Append(validParameter);
|
|
}
|
|
}
|
|
|
|
if (possibleParameters.Length > 0)
|
|
{
|
|
WriteCommandLineError(
|
|
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidArgument, args[i]),
|
|
showBanner: false);
|
|
WriteCommandLineError(possibleParameters.ToString(), showBanner: false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
WriteCommandLineError(
|
|
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.ArgumentFileDoesNotExist, args[i]),
|
|
showHelp: true);
|
|
return false;
|
|
}
|
|
|
|
i++;
|
|
|
|
string pendingParameter = null;
|
|
|
|
// Accumulate the arguments to this script...
|
|
while (i < args.Length)
|
|
{
|
|
string arg = args[i];
|
|
|
|
// If there was a pending parameter, add a named parameter
|
|
// using the pending parameter and current argument
|
|
if (pendingParameter != null)
|
|
{
|
|
_collectedArgs.Add(new CommandParameter(pendingParameter, arg));
|
|
pendingParameter = null;
|
|
}
|
|
else if (!string.IsNullOrEmpty(arg) && SpecialCharacters.IsDash(arg[0]) && arg.Length > 1)
|
|
{
|
|
int offset = arg.IndexOf(':');
|
|
if (offset >= 0)
|
|
{
|
|
if (offset == arg.Length - 1)
|
|
{
|
|
pendingParameter = arg.TrimEnd(':');
|
|
}
|
|
else
|
|
{
|
|
string argValue = arg.Substring(offset + 1);
|
|
string argName = arg.Substring(0, offset);
|
|
if (TryGetBoolValue(argValue, out bool boolValue))
|
|
{
|
|
_collectedArgs.Add(new CommandParameter(argName, boolValue));
|
|
}
|
|
else
|
|
{
|
|
_collectedArgs.Add(new CommandParameter(argName, argValue));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_collectedArgs.Add(new CommandParameter(arg));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_collectedArgs.Add(new CommandParameter(null, arg));
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEncoded)
|
|
{
|
|
if (_commandLineCommand != null)
|
|
{
|
|
// we've already set the command, so squawk
|
|
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.CommandAlreadySpecified);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.MissingCommandParameter);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
|
|
if (isEncoded)
|
|
{
|
|
try
|
|
{
|
|
_commandLineCommand = StringToBase64Converter.Base64ToString(args[i]);
|
|
}
|
|
// decoding failed
|
|
catch
|
|
{
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.BadCommandValue);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
}
|
|
else if (args[i] == "-")
|
|
{
|
|
// the arg to -command is -, which is secret code for "read the commands from stdin with no prompts"
|
|
|
|
_explicitReadCommandsFromStdin = true;
|
|
_noPrompt = true;
|
|
|
|
++i;
|
|
if (i != args.Length)
|
|
{
|
|
// there are more parameters to -command than -, which is an error.
|
|
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.TooManyParametersToCommand);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
|
|
if (!Console.IsInputRedirected)
|
|
{
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.StdinNotRedirected);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Collect the remaining parameters and combine them into a single command to be run.
|
|
|
|
StringBuilder cmdLineCmdSB = new StringBuilder();
|
|
|
|
while (i < args.Length)
|
|
{
|
|
cmdLineCmdSB.Append(args[i] + " ");
|
|
++i;
|
|
}
|
|
|
|
if (cmdLineCmdSB.Length > 0)
|
|
{
|
|
// remove the last blank
|
|
cmdLineCmdSB.Remove(cmdLineCmdSB.Length - 1, 1);
|
|
}
|
|
|
|
_commandLineCommand = cmdLineCmdSB.ToString();
|
|
}
|
|
|
|
if (!noexitSeen && !_explicitReadCommandsFromStdin)
|
|
{
|
|
// don't reset this if they've already specified -noexit
|
|
_noExit = false;
|
|
}
|
|
|
|
_showBanner = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool CollectArgs(string[] args, ref int i)
|
|
{
|
|
if (_collectedArgs.Count != 0)
|
|
{
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.ArgsAlreadySpecified);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
|
|
++i;
|
|
if (i >= args.Length)
|
|
{
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.MissingArgsValue);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
object[] a = StringToBase64Converter.Base64ToArgsConverter(args[i]);
|
|
if (a != null)
|
|
{
|
|
foreach (object obj in a)
|
|
{
|
|
_collectedArgs.Add(new CommandParameter(null, obj));
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// decoding failed
|
|
|
|
_hostUI.WriteErrorLine(CommandLineParameterParserStrings.BadArgsValue);
|
|
_showHelp = true;
|
|
_abortStartup = true;
|
|
_exitCode = ConsoleHost.ExitCodeBadCommandLineParameter;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool _socketServerMode;
|
|
private bool _serverMode;
|
|
private bool _namedPipeServerMode;
|
|
private bool _sshServerMode;
|
|
private bool _showVersion;
|
|
private string _configurationName;
|
|
private PSHostUserInterface _hostUI;
|
|
private bool _showHelp;
|
|
private bool _showExtendedHelp;
|
|
private bool _showBanner = true;
|
|
private bool _noInteractive;
|
|
private string _bannerText;
|
|
private string _helpText;
|
|
private bool _abortStartup;
|
|
private bool _skipUserInit;
|
|
private string _customPipeName;
|
|
#if STAMODE
|
|
// Win8: 182409 PowerShell 3.0 should run in STA mode by default
|
|
// -sta and -mta are mutually exclusive..so tracking them using nullable boolean
|
|
// if true, then sta is specified on the command line.
|
|
// if false, then mta is specified on the command line.
|
|
// if null, then none is specified on the command line..use default in this case
|
|
// default is sta.
|
|
private bool? _staMode = null;
|
|
#endif
|
|
private bool _noExit = true;
|
|
private bool _explicitReadCommandsFromStdin;
|
|
private bool _noPrompt;
|
|
private string _commandLineCommand;
|
|
private bool _wasCommandEncoded;
|
|
private uint _exitCode = ConsoleHost.ExitCodeSuccess;
|
|
private bool _dirty;
|
|
private Serialization.DataFormat _outFormat = Serialization.DataFormat.Text;
|
|
private bool _outputFormatSpecified = false;
|
|
private Serialization.DataFormat _inFormat = Serialization.DataFormat.Text;
|
|
private Collection<CommandParameter> _collectedArgs = new Collection<CommandParameter>();
|
|
private string _file;
|
|
private string _executionPolicy;
|
|
private string _workingDirectory;
|
|
|
|
#if !UNIX
|
|
private bool _removeWorkingDirectoryTrailingCharacter = false;
|
|
#endif
|
|
}
|
|
} // namespace
|
|
|