source: trunk/eraser/Eraser/Program.cs @ 2009

Revision 2009, 21.8 KB checked in by lowjoel, 5 years ago (diff)

Also parse normal parameters passed on the command line!

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Windows.Forms;
25
26using System.IO;
27using System.Threading;
28using System.Globalization;
29using System.ComponentModel;
30using System.Runtime.Serialization;
31using System.Security.Principal;
32using System.Text.RegularExpressions;
33
34using System.Reflection;
35using System.Diagnostics;
36
37using ComLib.Arguments;
38
39using Eraser.Manager;
40using Eraser.Util;
41
42namespace Eraser
43{
44    internal static partial class Program
45    {
46        /// <summary>
47        /// The common program arguments shared between the GUI and console programs.
48        /// </summary>
49        class Arguments
50        {
51            /// <summary>
52            /// True if the program should not be started with any user-visible interfaces.
53            /// </summary>
54            /// <remarks>Errors will also be silently ignored.</remarks>
55            [Arg("quiet", "The program should not be started with any user-visible interfaces. " +
56                "Errors will be silently ignored.", typeof(bool), false, false, null)]
57            public bool Quiet { get; set; }
58        }
59
60        /// <summary>
61        /// Program arguments which only apply to the GUI program.
62        /// </summary>
63        class GuiArguments : Arguments
64        {
65            /// <summary>
66            /// True if the command line specified atRestart, which should result in the
67            /// queueing of tasks meant for running at restart.
68            /// </summary>
69            [Arg("atRestart", "The program should queue all tasks scheduled for running at " +
70                "the system restart.", typeof(bool), false, false, null)]
71            public bool AtRestart { get; set; }
72        }
73
74        class ConsoleArguments : Arguments
75        {
76            /// <summary>
77            /// The Action which this handler is in charge of.
78            /// </summary>
79            [Arg(0, "The action this command line is stating.", typeof(string), true, null, null)]
80            public string Action { get; set; }
81
82            /// <summary>
83            /// The list of command line parameters not placed in a switch.
84            /// </summary>
85            public List<string> PositionalArguments { get; set; }
86        }
87
88        class AddTaskArguments : ConsoleArguments
89        {
90            /// <summary>
91            /// The erasure method which the user specified on the command line.
92            /// </summary>
93            [Arg("method", "The erasure method to use", typeof(Guid), false, null, null)]
94            public Guid ErasureMethod { get; set; }
95
96            /// <summary>
97            /// The schedule for the current set of targets.
98            /// </summary>
99            [Arg("schedule", "The schedule to use", typeof(Schedule), false, null, null)]
100            public string Schedule { get; set; }
101        }
102
103        /// <summary>
104        /// The main entry point for the application.
105        /// </summary>
106        [STAThread]
107        static int Main(string[] rawCommandLine)
108        {
109            //Immediately parse command line arguments. Start by substituting all
110            //response files ("@filename") arguments with the arguments found in the
111            //file
112            List<string> commandLine = new List<string>(rawCommandLine.Length);
113            foreach (string argument in rawCommandLine)
114            {
115                if (argument[0] == '@' && File.Exists(argument.Substring(1)))
116                {
117                    //The current parameter is a response file, parse the file
118                    //for arguments and substitute it.
119                    using (TextReader reader = new StreamReader(argument.Substring(1)))
120                    {
121                        commandLine.AddRange(Shell.ParseCommandLine(reader.ReadToEnd()));
122                    }
123                }
124                else
125                    commandLine.Add(argument);
126            }
127
128            string[] finalCommandLine = commandLine.ToArray();
129            ComLib.BoolMessageItem argumentParser = Args.Parse(finalCommandLine,
130                CommandLinePrefixes, CommandLineSeparators);
131            Args parsedArguments = (Args)argumentParser.Item;
132
133            //We default to a GUI if:
134            // - The parser did not succeed.
135            // - The parser resulted in an empty arguments list
136            // - The parser's argument at index 0 is not equal to the first argument (this
137            //   is when the user is passing GUI options -- command line options always
138            //   start with the action, e.g. Eraser help, or Eraser addtask
139            if (!argumentParser.Success || parsedArguments.IsEmpty ||
140                parsedArguments.Positional.Count == 0 ||
141                parsedArguments.Positional[0] != parsedArguments.Raw[0])
142            {
143                GUIMain(finalCommandLine);
144            }
145            else
146            {
147                return CommandMain(finalCommandLine);
148            }
149
150            //Return zero to signify success
151            return 0;
152        }
153
154        #region Console Program code
155        /// <summary>
156        /// Runs Eraser as a command-line application.
157        /// </summary>
158        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
159        private static int CommandMain(string[] commandLine)
160        {
161            using (ConsoleProgram program = new ConsoleProgram(commandLine))
162            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
163                try
164                {
165                    program.Handlers.Add("help",
166                        new ConsoleActionData(CommandHelp, new ConsoleArguments()));
167                    program.Handlers.Add("querymethods",
168                        new ConsoleActionData(CommandQueryMethods, new ConsoleArguments()));
169                    program.Handlers.Add("addtask",
170                        new ConsoleActionData(CommandAddTask, new AddTaskArguments()));
171                    program.Handlers.Add("importtasklist",
172                        new ConsoleActionData(CommandImportTaskList, new ConsoleArguments()));
173                    program.Run();
174                    return 0;
175                }
176                catch (UnauthorizedAccessException)
177                {
178                    return Win32ErrorCode.AccessDenied;
179                }
180                catch (Win32Exception e)
181                {
182                    Console.WriteLine(e.Message);
183                    return e.ErrorCode;
184                }
185                catch (Exception e)
186                {
187                    Console.WriteLine(e.Message);
188                    return 1;
189                }
190        }
191
192        /// <summary>
193        /// Prints the command line help for Eraser.
194        /// </summary>
195        private static void PrintCommandHelp()
196        {
197            Console.WriteLine(@"usage: Eraser <action> <arguments>
198where action is
199  help                Show this help message.
200  addtask             Adds tasks to the current task list.
201  querymethods        Lists all registered Erasure methods.
202  importtasklist      Imports an Eraser Task list to the current user's Task
203                      List.
204
205global parameters:
206  /quiet              Do not create a Console window to display progress.
207
208parameters for help:
209  eraser help
210
211  no parameters to set.
212
213parameters for addtask:
214  eraser addtask [/method=<methodGUID>] [/schedule=(now|manually|restart)] (recyclebin | unused=<volume> | dir=<directory> | file=<file>)[...]
215
216  /method             The Erasure method to use.
217  /schedule           The schedule the task will follow. The value must be one
218                      of:
219      now             The task will be queued for immediate execution.
220      manually        The task will be created but not queued for execution.
221      restart         The task will be queued for execution when the computer
222                      is next restarted.
223  recyclebin          Erases files and folders in the recycle bin
224  unused              Erases unused space in the volume.
225    optional arguments: unused=<drive>[,clusterTips[=(true|false)]]
226      clusterTips     If specified, the drive's files will have their
227                      cluster tips erased. This parameter accepts a Boolean
228                      value (true/false) as an argument; if none is specified
229                      true is assumed.
230  dir                 Erases files and folders in the directory
231    optional arguments: dir=<directory>[,-excludeMask][,+includeMask][,deleteIfEmpty]
232      excludeMask     A wildcard expression for files and folders to
233                      exclude.
234      includeMask     A wildcard expression for files and folders to
235                      include.
236                      The include mask is applied before the exclude mask.
237      deleteIfEmpty   Deletes the folder at the end of the erasure if it is
238                      empty.
239  file                Erases the specified file
240
241parameters for querymethods:
242  eraser querymethods
243
244  no parameters to set.
245
246parameters for importtasklist:
247  eraser importtasklist (file)[...]
248
249  [file]              A list of one or more files to import.
250
251All arguments are case sensitive.
252
253Response files can be used for very long command lines (generally, anything
254involving more than 32,000 characters.) Response files are used by prepending
255""@"" to the path to the file, and passing it into the command line. The
256contents of the response files' will be substituted at the same position into
257the command line.");
258            Console.Out.Flush();
259        }
260
261        /// <summary>
262        /// Prints the help text for Eraser (with copyright)
263        /// </summary>
264        /// <param name="arguments">Not used.</param>
265        private static void CommandHelp(ConsoleArguments arguments)
266        {
267            Console.WriteLine(@"Eraser {0}
268(c) 2008-2010 The Eraser Project
269Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
270", Assembly.GetExecutingAssembly().GetName().Version);
271
272            PrintCommandHelp();
273        }
274
275        /// <summary>
276        /// Lists all registered erasure methods.
277        /// </summary>
278        /// <param name="arguments">Not used.</param>
279        private static void CommandQueryMethods(ConsoleArguments arguments)
280        {
281            //Output the header
282            const string methodFormat = "{0,-2} {1,-39} {2}";
283            Console.WriteLine(methodFormat, "", "Method", "GUID");
284            Console.WriteLine(new string('-', 79));
285
286            //Refresh the list of erasure methods
287            foreach (ErasureMethod method in ManagerLibrary.Instance.ErasureMethodRegistrar)
288            {
289                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
290                    "U" : "", method.Name, method.Guid.ToString());
291            }
292        }
293
294        /// <summary>
295        /// Parses the command line for tasks and adds them using the
296        /// <see cref="RemoteExecutor"/> class.
297        /// </summary>
298        /// <param name="arg">The command line parameters passed to the program.</param>
299        private static void CommandAddTask(ConsoleArguments arg)
300        {
301            AddTaskArguments arguments = (AddTaskArguments)arg;
302
303            //Create the task then set the method as well as schedule
304            Task task = new Task();
305            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ?
306                ErasureMethodRegistrar.Default :
307                ManagerLibrary.Instance.ErasureMethodRegistrar[arguments.ErasureMethod];
308            switch (arguments.Schedule.ToUpperInvariant())
309            {
310                case "":
311                case "NOW":
312                    task.Schedule = Schedule.RunNow;
313                    break;
314                case "MANUALLY":
315                    task.Schedule = Schedule.RunManually;
316                    break;
317                case "RESTART":
318                    task.Schedule = Schedule.RunOnRestart;
319                    break;
320                default:
321                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
322                        "Unknown schedule type: {0}", arguments.Schedule), "/schedule");
323            }
324
325            //Parse the rest of the command line parameters as target expressions.
326            List<string> trueValues = new List<string>(new string[] { "yes", "true" });
327            string[] strings = new string[] {
328                //The recycle bin target
329                "(?<recycleBin>recyclebin)",
330
331                //The unused space erasure target, taking the optional clusterTips
332                //argument which defaults to true; if none is specified it's assumed
333                //false
334                "unused=(?<unusedVolume>.*)(?<unusedTips>,clusterTips(=(?<unusedTipsValue>true|false))?)?",
335
336                //The directory target, taking a list of + and - wildcard expressions.
337                "dir=(?<directoryName>.*)(?<directoryParams>(?<directoryExcludeMask>,-[^,]+)|(?<directoryIncludeMask>,\\+[^,]+)|(?<directoryDeleteIfEmpty>,deleteIfEmpty(=(?<directoryDeleteIfEmptyValue>true|false))?))*",
338
339                //The file target.
340                "file=(?<fileName>.*)"
341            };
342
343            Regex regex = new Regex(string.Join("|", strings),
344                RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
345            foreach (string argument in arguments.PositionalArguments)
346            {
347                Match match = regex.Match(argument);
348                if (match.Captures.Count == 0)
349                {
350                    Console.WriteLine("Unknown argument: {0}, skipped.", argument);
351                    continue;
352                }
353
354                ErasureTarget target = null;
355                if (match.Groups["recycleBin"].Success)
356                {
357                    target = new RecycleBinTarget();
358                }
359                else if (match.Groups["unusedVolume"].Success)
360                {
361                    UnusedSpaceTarget unusedSpaceTarget = new UnusedSpaceTarget();
362                    target = unusedSpaceTarget;
363                    unusedSpaceTarget.Drive = match.Groups["unusedVolume"].Value;
364
365                    if (!match.Groups["unusedTips"].Success)
366                        unusedSpaceTarget.EraseClusterTips = false;
367                    else if (!match.Groups["unusedTipsValue"].Success)
368                        unusedSpaceTarget.EraseClusterTips = true;
369                    else
370                        unusedSpaceTarget.EraseClusterTips =
371                            trueValues.IndexOf(match.Groups["unusedTipsValue"].Value) != -1;
372                }
373                else if (match.Groups["directoryName"].Success)
374                {
375                    FolderTarget folderTarget = new FolderTarget();
376                    target = folderTarget;
377
378                    folderTarget.Path = match.Groups["directoryName"].Value;
379                    if (!match.Groups["directoryDeleteIfEmpty"].Success)
380                        folderTarget.DeleteIfEmpty = false;
381                    else if (!match.Groups["directoryDeleteIfEmptyValue"].Success)
382                        folderTarget.DeleteIfEmpty = true;
383                    else
384                        folderTarget.DeleteIfEmpty =
385                            trueValues.IndexOf(match.Groups["directoryDeleteIfEmptyValue"].Value) != -1;
386                    if (match.Groups["directoryExcludeMask"].Success)
387                        folderTarget.ExcludeMask += match.Groups["directoryExcludeMask"].Value.Remove(0, 2) + ' ';
388                    if (match.Groups["directoryIncludeMask"].Success)
389                        folderTarget.IncludeMask += match.Groups["directoryIncludeMask"].Value.Remove(0, 2) + ' ';
390                }
391                else if (match.Groups["fileName"].Success)
392                {
393                    FileTarget fileTarget = new FileTarget();
394                    target = fileTarget;
395                    fileTarget.Path = match.Groups["fileName"].Value;
396                }
397
398                if (target == null)
399                    continue;
400
401                target.Method = method;
402                task.Targets.Add(target);
403            }
404
405            //Check the number of tasks in the task.
406            if (task.Targets.Count == 0)
407                throw new ArgumentException("Tasks must contain at least one erasure target.");
408
409            //Send the task out.
410            try
411            {
412                using (RemoteExecutorClient client = new RemoteExecutorClient())
413                {
414                    client.Run();
415                    if (!client.IsConnected)
416                    {
417                        //The client cannot connect to the server. This probably means
418                        //that the server process isn't running. Start an instance.
419                        Process eraserInstance = Process.Start(
420                            Assembly.GetExecutingAssembly().Location, "/quiet");
421                        Thread.Sleep(0);
422                        eraserInstance.WaitForInputIdle();
423
424                        client.Run();
425                        if (!client.IsConnected)
426                            throw new IOException("Eraser cannot connect to the running " +
427                                "instance for erasures.");
428                    }
429
430                    client.Tasks.Add(task);
431                }
432            }
433            catch (UnauthorizedAccessException e)
434            {
435                //We can't connect to the pipe because the other instance of Eraser
436                //is running with higher privileges than this instance.
437                throw new UnauthorizedAccessException("Another instance of Eraser " +
438                    "is already running but it is running with higher privileges than " +
439                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
440                    "Close the running instance of Eraser and start it again without " +
441                    "administrator privileges, or run the command again as an " +
442                    "administrator.", e);
443            }
444        }
445
446        /// <summary>
447        /// Imports the given tasklists and adds them to the global Eraser instance.
448        /// </summary>
449        /// <param name="args">The list of files specified on the command line.</param>
450        private static void CommandImportTaskList(ConsoleArguments args)
451        {
452            //Import the task list
453            try
454            {
455                using (RemoteExecutorClient client = new RemoteExecutorClient())
456                {
457                    client.Run();
458                    if (!client.IsConnected)
459                    {
460                        //The client cannot connect to the server. This probably means
461                        //that the server process isn't running. Start an instance.
462                        Process eraserInstance = Process.Start(
463                            Assembly.GetExecutingAssembly().Location, "/quiet");
464                        eraserInstance.WaitForInputIdle();
465
466                        client.Run();
467                        if (!client.IsConnected)
468                            throw new IOException("Eraser cannot connect to the running " +
469                                "instance for erasures.");
470                    }
471
472                    foreach (string path in args.PositionalArguments)
473                        using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
474                            client.Tasks.LoadFromStream(stream);
475                }
476            }
477            catch (UnauthorizedAccessException e)
478            {
479                //We can't connect to the pipe because the other instance of Eraser
480                //is running with higher privileges than this instance.
481                throw new UnauthorizedAccessException("Another instance of Eraser " +
482                    "is already running but it is running with higher privileges than " +
483                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
484                    "Close the running instance of Eraser and start it again without " +
485                    "administrator privileges, or run the command again as an " +
486                    "administrator.", e);
487            }
488        }
489        #endregion
490
491        #region GUI Program code
492        /// <summary>
493        /// Runs Eraser as a GUI application.
494        /// </summary>
495        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
496        private static void GUIMain(string[] commandLine)
497        {
498            //Create a unique program instance ID for this user.
499            string instanceId = "Eraser-BAD0DAC6-C9EE-4acc-8701-C9B3C64BC65E-GUI-" +
500                WindowsIdentity.GetCurrent().User.ToString();
501
502            //Then initialise the instance and initialise the Manager library.
503            using (GuiProgram program = new GuiProgram(commandLine, instanceId))
504            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
505            {
506                program.InitInstance += OnGUIInitInstance;
507                program.NextInstance += OnGUINextInstance;
508                program.ExitInstance += OnGUIExitInstance;
509                program.Run();
510            }
511        }
512
513        /// <summary>
514        /// Triggered when the Program is started for the first time.
515        /// </summary>
516        /// <param name="sender">The sender of the object.</param>
517        /// <param name="e">Event arguments.</param>
518        private static void OnGUIInitInstance(object sender, InitInstanceEventArgs e)
519        {
520            GuiProgram program = (GuiProgram)sender;
521            eraserClient = new RemoteExecutorServer();
522
523            //Set our UI language
524            EraserSettings settings = EraserSettings.Get();
525            Thread.CurrentThread.CurrentUICulture = new CultureInfo(settings.Language);
526            Application.SafeTopLevelCaptionFormat = S._("Eraser");
527
528            //Load the task list
529            try
530            {
531                if (File.Exists(TaskListPath))
532                {
533                    using (FileStream stream = new FileStream(TaskListPath, FileMode.Open,
534                        FileAccess.Read, FileShare.Read))
535                    {
536                        eraserClient.Tasks.LoadFromStream(stream);
537                    }
538                }
539            }
540            catch (InvalidDataException ex)
541            {
542                File.Delete(TaskListPath);
543                MessageBox.Show(S._("Could not load task list. All task entries have " +
544                    "been lost. The error returned was: {0}", ex.Message), S._("Eraser"),
545                    MessageBoxButtons.OK, MessageBoxIcon.Error,
546                    MessageBoxDefaultButton.Button1,
547                    Localisation.IsRightToLeft(null) ?
548                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
549            }
550
551            //Create the main form
552            program.MainForm = new MainForm();
553
554            //Decide whether to display any UI
555            GuiArguments arguments = new GuiArguments();
556            Args.Parse(program.CommandLine, CommandLinePrefixes, CommandLineSeparators, arguments);
557            e.ShowMainForm = !arguments.AtRestart && !arguments.Quiet;
558
559            //Queue tasks meant for running at restart if we are given that command line.
560            if (arguments.AtRestart)
561                eraserClient.QueueRestartTasks();
562
563            //Run the eraser client.
564            eraserClient.Run();
565        }
566
567        /// <summary>
568        /// Triggered when a second instance of Eraser is started.
569        /// </summary>
570        /// <param name="sender">The sender of the event.</param>
571        /// <param name="e">Event argument.</param>
572        private static void OnGUINextInstance(object sender, NextInstanceEventArgs e)
573        {
574            //Another instance of the GUI Program has been started: show the main window
575            //now as we still do not have a facility to handle the command line arguments.
576            GuiProgram program = (GuiProgram)sender;
577
578            //Invoke the function if we aren't on the main thread
579            if (program.MainForm.InvokeRequired)
580            {
581                program.MainForm.Invoke(
582                    (GuiProgram.NextInstanceEventHandler)OnGUINextInstance,
583                    sender, e);
584                return;
585            }
586
587            program.MainForm.Show();
588        }
589
590        /// <summary>
591        /// Triggered when the first instance of Eraser is exited.
592        /// </summary>
593        /// <param name="sender">The sender of the event.</param>
594        /// <param name="e">Event argument.</param>
595        private static void OnGUIExitInstance(object sender, EventArgs e)
596        {
597            //Save the task list
598            if (!Directory.Exists(Program.AppDataPath))
599                Directory.CreateDirectory(Program.AppDataPath);
600            using (FileStream stream = new FileStream(TaskListPath, FileMode.Create,
601                FileAccess.Write, FileShare.None))
602            {
603                eraserClient.Tasks.SaveToStream(stream);
604            }
605
606            //Dispose the eraser executor instance
607            eraserClient.Dispose();
608        }
609        #endregion
610
611        /// <summary>
612        /// The acceptable list of command line prefixes we will accept.
613        /// </summary>
614        public const string CommandLinePrefixes = "(/|-|--)";
615
616        /// <summary>
617        /// The acceptable list of command line separators we will accept.
618        /// </summary>
619        public const string CommandLineSeparators = "(:|=)";
620
621        /// <summary>
622        /// The global Executor instance.
623        /// </summary>
624        public static Executor eraserClient;
625
626        /// <summary>
627        /// Path to the Eraser application data path.
628        /// </summary>
629        public static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(
630            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6");
631
632        /// <summary>
633        /// File name of the Eraser task list.
634        /// </summary>
635        private const string TaskListFileName = @"Task List.ersx";
636
637        /// <summary>
638        /// Path to the Eraser task list.
639        /// </summary>
640        public static readonly string TaskListPath = Path.Combine(AppDataPath, TaskListFileName);
641
642        /// <summary>
643        /// Path to the Eraser settings key (relative to HKCU)
644        /// </summary>
645        public const string SettingsPath = @"SOFTWARE\Eraser\Eraser 6";
646    }
647}
Note: See TracBrowser for help on using the repository browser.