in src/Cli/func/ConsoleApp.cs [128:201]
public static bool RelaunchSelfElevated(IAction action, out string errors)
{
// A command is:
// func <context: optional> \
// <subcontext: valid only if there is a context, optional> \
// <action: not optional> --options
errors = string.Empty;
var attribute = action.GetType().GetCustomAttribute<ActionAttribute>();
if (attribute != null)
{
// First extract the contexts for the given action
Func<Context, string> getContext = c => c == Context.None ? string.Empty : c.ToString();
var context = getContext(attribute.Context);
var subContext = getContext(attribute.Context);
// Get the actual action name to use on the command line.
var name = attribute.Name;
// Every action is expected to return a ICommandLineParserResult that contains
// a collection UnMatchedOptions that the action accepts.
// That means this method doesn't support actions that have untyped ordered options.
// This however can be updated to support them easily just like help does now.
var args = action
.ParseArgs(Array.Empty<string>())
.UnMatchedOptions
// Description is expected to contain the name of the POCO's property holding the value.
.Select(o => new { Name = o.Description, ParamName = o.HasLongName ? $"--{o.LongName}" : $"-{o.ShortName}" })
.Select(n =>
{
var property = action.GetType().GetProperty(n.Name);
if (property.PropertyType.IsGenericEnumerable())
{
var genericCollection = property.GetValue(action) as IEnumerable;
var collection = genericCollection.Cast<object>().Select(o => o.ToString());
return $"{n.ParamName} {string.Join(" ", collection)}";
}
else
{
return $"{n.ParamName} {property.GetValue(action).ToString()}";
}
})
.Aggregate((a, b) => string.Join(" ", a, b));
var command = $"{context} {subContext} {name} {args}";
// Since the process will be elevated, we won't be able to redirect stdout\stdin to
// our process if we are not elevated too, which is most probably the case.
// Therefore I use shell redirection > to a temp file, then read the content after
// the process exists.
var logFile = Path.GetTempFileName();
var exeName = Process.GetCurrentProcess().MainModule.FileName;
// '2>&1' redirects stderr to stdout.
command = $"/c \"\"{exeName}\" {command} > \"{logFile}\" 2>&1\"";
var startInfo = new ProcessStartInfo("cmd")
{
Verb = "runas",
Arguments = command,
WorkingDirectory = Environment.CurrentDirectory,
CreateNoWindow = false,
UseShellExecute = true
};
var process = Process.Start(startInfo);
process.WaitForExit();
errors = File.ReadAllText(logFile);
return process.ExitCode == ExitCodes.Success;
}
else
{
throw new ArgumentException($"{nameof(IAction)} type doesn't have {nameof(ActionAttribute)}");
}
}