source: branches/eraser6/Eraser/Program.cs @ 788

Revision 788, 38.5 KB checked in by lowjoel, 6 years ago (diff)

Show a helpful message whenever Eraser tries to communicate with another instance of higher privileges (e.g., when a non-elevated process is communicating with an elevated process)

  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 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.IO.Pipes;
28using System.Text;
29using System.Threading;
30using System.Runtime.Serialization.Formatters.Binary;
31using System.Globalization;
32using System.Reflection;
33using System.Diagnostics;
34using System.ComponentModel;
35
36using Eraser.Manager;
37using Eraser.Util;
38using Microsoft.Win32;
39
40namespace Eraser
41{
42    static class Program
43    {
44        /// <summary>
45        /// The main entry point for the application.
46        /// </summary>
47        [STAThread]
48        static int Main(string[] commandLine)
49        {
50            //Trivial case: no command parameters
51            if (commandLine.Length == 0)
52                GUIMain(commandLine);
53
54            //Determine if the sole parameter is --restart; if it is, start the GUI
55            //passing isRestart as true. Otherwise, we're a console application.
56            else if (commandLine.Length == 1)
57            {
58                if (commandLine[0] == "--atRestart" || commandLine[0] == "--quiet")
59                {
60                    GUIMain(commandLine);
61                }
62                else
63                {
64                    return CommandMain(commandLine);
65                }
66            }
67
68            //The other trivial case: definitely a console application.
69            else
70                return CommandMain(commandLine);
71
72            //No error.
73            return 0;
74        }
75
76        /// <summary>
77        /// Runs Eraser as a command-line application.
78        /// </summary>
79        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
80        private static int CommandMain(string[] commandLine)
81        {
82            //True if the user specified a quiet command.
83            bool isQuiet = false;
84
85            try
86            {
87                CommandLineProgram program = new CommandLineProgram(commandLine);
88                isQuiet = program.Arguments.Quiet;
89
90                using (ManagerLibrary library = new ManagerLibrary(new Settings()))
91                    program.Run();
92
93                return 0;
94            }
95            catch (Win32Exception e)
96            {
97                Console.WriteLine(e.Message);
98                return e.ErrorCode;
99            }
100            catch (Exception e)
101            {
102                Console.WriteLine(e.Message);
103                return 1;
104            }
105            finally
106            {
107                //Flush the buffered output to the console
108                Console.Out.Flush();
109
110                //Don't ask for a key to press if the user specified Quiet
111                if (!isQuiet)
112                {
113                    Console.Write("\nPress enter to continue . . . ");
114                    Console.Out.Flush();
115                    Console.ReadLine();
116                }
117
118                KernelAPI.FreeConsole();
119            }
120        }
121
122        /// <summary>
123        /// Runs Eraser as a GUI application.
124        /// </summary>
125        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
126        private static void GUIMain(string[] commandLine)
127        {
128            //Create the program instance
129            GUIProgram program = new GUIProgram(commandLine, "Eraser-BAD0DAC6-C9EE-4acc-" +
130                "8701-C9B3C64BC65E-GUI-" +
131                System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString());
132
133            //Then run the program instance.
134            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
135            {
136                program.InitInstance += OnGUIInitInstance;
137                program.NextInstance += OnGUINextInstance;
138                program.ExitInstance += OnGUIExitInstance;
139                program.Run();
140            }
141        }
142
143        /// <summary>
144        /// Triggered when the Program is started for the first time.
145        /// </summary>
146        /// <param name="sender">The sender of the object.</param>
147        /// <returns>True if the user did not specify --quiet, false otherwise.</returns>
148        private static bool OnGUIInitInstance(object sender)
149        {
150            GUIProgram program = (GUIProgram)sender;
151            using (eraserClient = new RemoteExecutorServer())
152            {
153                //Set our UI language
154                EraserSettings settings = new EraserSettings();
155                System.Threading.Thread.CurrentThread.CurrentUICulture =
156                    new CultureInfo(settings.Language);
157                program.SafeTopLevelCaptionFormat = S._("Eraser");
158
159                //Load the task list
160                if (settings.TaskList != null)
161                    using (MemoryStream stream = new MemoryStream(settings.TaskList))
162                        try
163                        {
164                            eraserClient.LoadTaskList(stream);
165                        }
166                        catch (Exception)
167                        {
168                            settings.TaskList = null;
169                            MessageBox.Show(S._("Could not load task list. All task entries have " +
170                                "been lost."), S._("Eraser"), MessageBoxButtons.OK,
171                                MessageBoxIcon.Error);
172                        }
173
174                //Create the main form
175                program.MainForm = new MainForm();
176                program.MainForm.CreateControl();
177                bool showMainForm = true;
178                foreach (string param in program.CommandLine)
179                {
180                    //Run tasks which are meant to be run on restart
181                    switch (param)
182                    {
183                        case "--atRestart":
184                            eraserClient.QueueRestartTasks();
185                            goto case "--quiet";
186
187                        //Hide the main form if the user specified the quiet command
188                        //line
189                        case "--quiet":
190                            showMainForm = false;
191                            break;
192                    }
193                }
194
195                //Run the eraser client.
196                eraserClient.Run();
197                return showMainForm;
198            }
199        }
200
201        /// <summary>
202        /// Triggered when a second instance of Eraser is started.
203        /// </summary>
204        /// <param name="sender">The sender of the event.</param>
205        private static void OnGUINextInstance(object sender)
206        {
207            //Another instance of the GUI Program has been started: show the main window
208            //now as we still do not have a facility to handle the command line arguments.
209            GUIProgram program = (GUIProgram)sender;
210
211            //Invoke the function if we aren't on the main thread
212            if (program.MainForm.InvokeRequired)
213            {
214                program.MainForm.Invoke(new GUIProgram.NextInstanceFunction(
215                    OnGUINextInstance), new object[] { sender });
216                return;
217            }
218
219            program.MainForm.Visible = true;
220        }
221
222        /// <summary>
223        /// Triggered when the first instance of Eraser is exited.
224        /// </summary>
225        /// <param name="sender">The sender of the event.</param>
226        private static void OnGUIExitInstance(object sender)
227        {
228            //Save the task list
229            EraserSettings settings = new EraserSettings();
230            using (MemoryStream stream = new MemoryStream())
231            {
232                eraserClient.SaveTaskList(stream);
233                settings.TaskList = stream.ToArray();
234            }
235        }
236
237        /// <summary>
238        /// The global Executor instance.
239        /// </summary>
240        public static Executor eraserClient;
241    }
242
243    class GUIProgram
244    {
245        /// <summary>
246        /// Constructor.
247        /// </summary>
248        /// <param name="commandLine">The command line arguments associated with
249        /// this program launch</param>
250        /// <param name="instanceID">The instance ID of the program, used to group
251        /// instances of the program together.</param>
252        public GUIProgram(string[] commandLine, string instanceID)
253        {
254            Application.EnableVisualStyles();
255            Application.SetCompatibleTextRenderingDefault(false);
256            this.instanceID = instanceID;
257            this.commandLine = commandLine;
258
259            //Check if there already is another instance of the program.
260            globalMutex = new Mutex(true, instanceID, out isFirstInstance);
261        }
262
263        /// <summary>
264        /// Runs the event loop of the GUI program, returning true if the program
265        /// was started as there were no other instances of the program, or false
266        /// if other instances were found.
267        /// </summary>
268        /// <remarks>
269        /// This function must always be called in your program, regardless
270        /// of the value of <see cref="IsAlreadyRunning"/>. If this function is not
271        /// called, the first instance will never be notified that another was started.
272        /// </remarks>
273        /// <returns>True if the application was started, or false if another instance
274        /// was detected.</returns>
275        public bool Run()
276        {
277            //If no other instances are running, set up our pipe server so clients
278            //can connect and give us subsequent command lines.
279            if (isFirstInstance)
280            {
281                try
282                {
283                    //Create the pipe server which will handle connections to us
284                    pipeServer = new Thread(ServerMain);
285                    pipeServer.Start();
286
287                    //Initialise and run the program.
288                    if (OnInitInstance(this) && MainForm != null)
289                        Application.Run(MainForm);
290                    else
291                    {
292                        //If we aren't showing the form, force the creation of the window
293                        //handle.
294                        MainForm.CreateControl();
295                        IntPtr handle = MainForm.Handle;
296                        Application.Run();
297                    }
298                    return true;
299                }
300                finally
301                {
302                    pipeServer.Abort();
303                }
304            }
305
306            //Another instance of the program is running. Connect to it and transfer
307            //the command line arguments
308            else
309            {
310                try
311                {
312                    NamedPipeClientStream client = new NamedPipeClientStream(".", instanceID,
313                        PipeDirection.Out);
314                    client.Connect(500);
315
316                    StringBuilder commandLineStr = new StringBuilder(commandLine.Length * 64);
317                    foreach (string param in commandLine)
318                        commandLineStr.Append(string.Format("{0}\0", param));
319
320                    byte[] buffer = new byte[commandLineStr.Length];
321                    int count = Encoding.UTF8.GetBytes(commandLineStr.ToString(), 0,
322                        commandLineStr.Length, buffer, 0);
323                    client.Write(buffer, 0, buffer.Length);
324                }
325                catch (UnauthorizedAccessException)
326                {
327                    //We can't connect to the pipe because the other instance of Eraser
328                    //is running with higher privileges than this instance. Tell the
329                    //user this is the case and show him how to resolve the issue.
330                    MessageBox.Show(S._("Another instance of Eraser is already running but it is " +
331                        "running with higher privileges than this instance of Eraser.\n\n" +
332                        "Eraser will now exit."), S._("Eraser"), MessageBoxButtons.OK,
333                        MessageBoxIcon.Information);
334                }
335                catch (TimeoutException)
336                {
337                    //Can't do much: half a second is a reasonably long time to wait.
338                }
339                return false;
340            }
341        }
342
343        /// <summary>
344        /// Holds information required for an asynchronous call to
345        /// NamedPipeServerStream.BeginWaitForConnection.
346        /// </summary>
347        private struct ServerAsyncInfo
348        {
349            public NamedPipeServerStream Server;
350            public AutoResetEvent WaitHandle;
351        }
352
353        /// <summary>
354        /// Runs a background thread, monitoring for new connections to the server.
355        /// </summary>
356        private void ServerMain()
357        {
358            while (pipeServer.ThreadState != System.Threading.ThreadState.AbortRequested)
359            {
360                using (NamedPipeServerStream server = new NamedPipeServerStream(instanceID,
361                    PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous))
362                {
363                    ServerAsyncInfo async = new ServerAsyncInfo();
364                    async.Server = server;
365                    async.WaitHandle = new AutoResetEvent(false);
366                    IAsyncResult result = server.BeginWaitForConnection(WaitForConnection, async);
367
368                    //Wait for the operation to complete.
369                    if (result.AsyncWaitHandle.WaitOne())
370                        //It completed. Wait for the processing to complete.
371                        async.WaitHandle.WaitOne();
372                }
373            }
374        }
375
376        /// <summary>
377        /// Waits for new connections to be made to the server.
378        /// </summary>
379        /// <param name="result"></param>
380        private void WaitForConnection(IAsyncResult result)
381        {
382            ServerAsyncInfo async = (ServerAsyncInfo)result.AsyncState;
383
384            try
385            {
386                //We're done waiting for the connection
387                async.Server.EndWaitForConnection(result);
388
389                //Process the connection if the server was successfully connected.
390                if (async.Server.IsConnected)
391                {
392                    //Read the message from the secondary instance
393                    byte[] buffer = new byte[8192];
394                    string message = string.Empty;
395
396                    do
397                    {
398                        int lastRead = async.Server.Read(buffer, 0, buffer.Length);
399                        message += Encoding.UTF8.GetString(buffer, 0, lastRead);
400                    }
401                    while (!async.Server.IsMessageComplete);
402
403                    //Let the event handler process the message.
404                    OnNextInstance(this, message);
405                }
406            }
407            catch (ObjectDisposedException)
408            {
409            }
410            finally
411            {
412                //Reset the wait event
413                async.WaitHandle.Set();
414            }
415        }
416
417        /// <summary>
418        /// Gets or sets the format string to apply to top-level window captions when
419        /// they are displayed with a warning banner.
420        /// </summary>
421        public string SafeTopLevelCaptionFormat
422        {
423            get
424            {
425                return Application.SafeTopLevelCaptionFormat;
426            }
427            set
428            {
429                Application.SafeTopLevelCaptionFormat = value;
430            }
431        }
432
433        /// <summary>
434        /// Gets the command line arguments this instance was started with.
435        /// </summary>
436        public string[] CommandLine
437        {
438            get
439            {
440                return commandLine;
441            }
442        }
443
444        /// <summary>
445        /// Gets whether another instance of the program is already running.
446        /// </summary>
447        public bool IsAlreadyRunning
448        {
449            get
450            {
451                return isFirstInstance;
452            }
453        }
454
455        /// <summary>
456        /// The main form for this program instance. This form will be shown when
457        /// run is called if it is non-null and if its Visible property is true.
458        /// </summary>
459        public Form MainForm
460        {
461            get
462            {
463                return mainForm;
464            }
465            set
466            {
467                mainForm = value;
468            }
469        }
470
471        #region Events
472        /// <summary>
473        /// The prototype of event handlers procesing the InitInstance event.
474        /// </summary>
475        /// <param name="sender">The sender of the event.</param>
476        /// <returns>True if the MainForm property holds a valid reference to
477        /// a form, and the form should be displayed to the user.</returns>
478        public delegate bool InitInstanceFunction(object sender);
479
480        /// <summary>
481        /// The event object managing listeners to the instance initialisation event.
482        /// This event is raised when the first instance of the program is started
483        /// and this is where the program initialisation code should be.
484        /// </summary>
485        public event InitInstanceFunction InitInstance;
486
487        /// <summary>
488        /// Broadcasts the InitInstance event.
489        /// </summary>
490        /// <param name="sender">The sender of the event.</param>
491        /// <returns>True if the MainForm object should be shown.</returns>
492        private bool OnInitInstance(object sender)
493        {
494            if (InitInstance != null)
495                return InitInstance(sender);
496            return true;
497        }
498
499        /// <summary>
500        /// The prototype of event handlers procesing the NextInstance event.
501        /// </summary>
502        /// <param name="sender">The sender of the event</param>
503        public delegate void NextInstanceFunction(object sender);
504
505        /// <summary>
506        /// The event object managing listeners to the next instance event. This
507        /// event is raised when a second instance of the program is started.
508        /// </summary>
509        public event NextInstanceFunction NextInstance;
510
511        /// <summary>
512        /// Broadcasts the NextInstance event.
513        /// </summary>
514        /// <param name="sender">The sender of the event.</param>
515        /// <param name="message">The message sent by the secondary instance.</param>
516        private void OnNextInstance(object sender, string message)
517        {
518            if (NextInstance != null)
519                NextInstance(sender);
520        }
521
522        /// <summary>
523        /// The prototype of event handlers procesing the ExitInstance event.
524        /// </summary>
525        /// <param name="sender">The sender of the event.</param>
526        public delegate void ExitInstanceFunction(object sender);
527
528        /// <summary>
529        /// The event object managing listeners to the exit instance event. This
530        /// event is raised when the first instance of the program is exited.
531        /// </summary>
532        public event ExitInstanceFunction ExitInstance;
533
534        /// <summary>
535        /// Broadcasts the ExitInstance event.
536        /// </summary>
537        /// <param name="sender">The sender of the event.</param>
538        private void OnExitInstance(object sender)
539        {
540            if (ExitInstance != null)
541                ExitInstance(sender);
542        }
543        #endregion
544
545        #region Instance variables
546        /// <summary>
547        /// The Instance ID of this program, used to group program instances together.
548        /// </summary>
549        private string instanceID;
550
551        /// <summary>
552        /// The named mutex ensuring that only one instance of the application runs
553        /// at a time.
554        /// </summary>
555        private Mutex globalMutex;
556
557        /// <summary>
558        /// The thread maintaining the pipe server for secondary instances to connect to.
559        /// </summary>
560        private Thread pipeServer;
561
562        /// <summary>
563        /// Holds whether this instance of the program is the first instance.
564        /// </summary>
565        private bool isFirstInstance;
566
567        /// <summary>
568        /// The command line arguments this instance was started with.
569        /// </summary>
570        private string[] commandLine;
571
572        /// <summary>
573        /// The main form for this program instance.
574        /// </summary>
575        private Form mainForm;
576        #endregion
577    }
578
579    class CommandLineProgram
580    {
581        #region Command Line parsing classes
582        /// <summary>
583        /// Manages a command line.
584        /// </summary>
585        public class CommandLine
586        {
587            /// <summary>
588            /// Constructor.
589            /// </summary>
590            /// <param name="cmdParams">The raw arguments passed to the program.</param>
591            public CommandLine(string[] cmdParams)
592            {
593                //Get the action.
594                if (cmdParams.Length < 1)
595                    throw new ArgumentException("An action must be specified.");
596                Action = cmdParams[0];
597
598                //Iterate over each argument, resolving them ourselves and letting
599                //subclasses resolve them if we don't know how to.
600                for (int i = 1; i != cmdParams.Length; ++i)
601                {
602                    if (IsParam(cmdParams[i], "quiet", "q"))
603                        Quiet = true;
604                    else if (!ResolveParameter(cmdParams[i]))
605                        throw new ArgumentException("Unknown argument: " + cmdParams[i]);
606                }
607            }
608
609            /// <summary>
610            /// Called when a parameter is not used by the current CommandLine object
611            /// for subclasses to handle their parameters.
612            /// </summary>
613            /// <param name="param">The parameter to resolve.</param>
614            /// <returns>Return true if the parameter was resolved and accepted.</returns>
615            virtual protected bool ResolveParameter(string param)
616            {
617                return false;
618            }
619
620            /// <summary>
621            /// Checks if the given <paramref name="parameter"/> refers to the
622            /// <paramref name="expectedParameter"/>, regardless of whether it is specified
623            /// with -, --, or /
624            /// </summary>
625            /// <param name="parameter">The parameter on the command line.</param>
626            /// <param name="expectedParameter">The parameter the program is looking for, without
627            /// the - or / prefix.</param>
628            /// <param name="shortParameter">The short parameter when used with a single hyphen,
629            /// without the - or / prefix.</param>
630            /// <returns>True if the parameter references the given expected parameter.</returns>
631            protected static bool IsParam(string parameter, string expectedParameter,
632                string shortParameter)
633            {
634                //Trivial case
635                if (parameter.Length < 1)
636                    return false;
637
638                //Extract the bits before the equal sign.
639                {
640                    int equalPos = parameter.IndexOf('=');
641                    if (equalPos != -1)
642                        parameter = parameter.Substring(0, equalPos);
643                }
644
645                //Get the first letter.
646                switch (parameter[0])
647                {
648                    case '-':
649                        //Can be a - or a --. Check for the second parameter
650                        if (parameter.Length < 2)
651                            //Nothing specified at the end... it's invalid.
652                            return false;
653
654                        if (parameter[1] == '-')
655                            return parameter.Substring(2) == expectedParameter;
656                        else if (shortParameter == null || shortParameter == string.Empty)
657                            return parameter.Substring(1) == expectedParameter;
658                        else
659                            return parameter.Substring(1) == shortParameter;
660
661                    case '/':
662                        //The / can be used with both long and short parameters.
663                        parameter = parameter.Substring(1);
664                        return parameter == expectedParameter || (
665                            shortParameter != null && shortParameter != string.Empty &&
666                            parameter == shortParameter
667                        );
668
669                    default:
670                        return false;
671                }
672            }
673
674            /// <summary>
675            /// Gets the list of subparameters of the parameter. Subparameters are text
676            /// after the first =, separated by commas.
677            /// </summary>
678            /// <param name="param">The subparameter text to parse.</param>
679            /// <returns>The list of subparameters in the parameter.</returns>
680            protected static List<KeyValuePair<string, string>> GetSubParameters(string param)
681            {
682                List<KeyValuePair<string, string>> result =
683                    new List<KeyValuePair<string, string>>();
684                int lastPos = 0;
685                int commaPos = (param += ',').IndexOf(',');
686
687                while (commaPos != -1)
688                {
689                    //Extract the current subparameter, and dissect the subparameter at
690                    //the first =.
691                    string subParam = param.Substring(lastPos, commaPos - lastPos);
692                    int equalPos = subParam.IndexOf('=');
693                    if (equalPos == -1)
694                        result.Add(new KeyValuePair<string, string>(subParam, null));
695                    else
696                        result.Add(new KeyValuePair<string, string>(subParam.Substring(0, equalPos),
697                            subParam.Substring(equalPos + 1)));
698
699                    //Find the next ,
700                    lastPos = ++commaPos;
701                    commaPos = param.IndexOf(',', commaPos);
702                }
703
704                return result;
705            }
706
707            /// <summary>
708            /// The action that the command line specifies.
709            /// </summary>
710            public string Action
711            {
712                get
713                {
714                    return action;
715                }
716                private set
717                {
718                    action = value;
719                }
720            }
721
722            /// <summary>
723            /// True if no console window should be created.
724            /// </summary>
725            public bool Quiet
726            {
727                get
728                {
729                    return quiet;
730                }
731                private set
732                {
733                    quiet = value;
734                }
735            }
736
737            private string action;
738            private bool quiet;
739        }
740
741        /// <summary>
742        /// Manages a command line for adding tasks to the global DirectExecutor
743        /// </summary>
744        class AddTaskCommandLine : CommandLine
745        {
746            /// <summary>
747            /// Constructor.
748            /// </summary>
749            /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
750            public AddTaskCommandLine(string[] cmdParams)
751                : base(cmdParams)
752            {
753            }
754
755            protected override bool ResolveParameter(string param)
756            {
757                int equalPos = param.IndexOf('=');
758                if (IsParam(param, "method", "m"))
759                {
760                    if (equalPos == -1)
761                        throw new ArgumentException("--method must be specified with an Erasure " +
762                            "method GUID.");
763
764                    List<KeyValuePair<string, string>> subParams =
765                        GetSubParameters(param.Substring(equalPos + 1));
766                    ErasureMethod = new Guid(subParams[0].Key);
767                }
768                else if (IsParam(param, "schedule", "s"))
769                {
770                    if (equalPos == -1)
771                        throw new ArgumentException("--schedule must be specified with a Schedule " +
772                            "type.");
773
774                    List<KeyValuePair<string, string>> subParams =
775                        GetSubParameters(param.Substring(equalPos + 1));
776                    switch (subParams[0].Key)
777                    {
778                        case "now":
779                            Schedule = Schedule.RunNow;
780                            break;
781                        case "restart":
782                            schedule = Schedule.RunOnRestart;
783                            break;
784                        default:
785                            throw new ArgumentException("Unknown schedule type: " + subParams[0].Key);
786                    }
787                }
788                else if (IsParam(param, "recycled", "r"))
789                {
790                    targets.Add(new Task.RecycleBin());
791                }
792                else if (IsParam(param, "unused", "u"))
793                {
794                    if (equalPos == -1)
795                        throw new ArgumentException("--unused must be specified with the Volume " +
796                            "to erase.");
797
798                    //Create the UnusedSpace target for inclusion into the task.
799                    Task.UnusedSpace target = new Task.UnusedSpace();
800
801                    //Determine if cluster tips should be erased.
802                    target.EraseClusterTips = false;
803                    List<KeyValuePair<string, string>> subParams =
804                        GetSubParameters(param.Substring(equalPos + 1));
805                    foreach (KeyValuePair<string, string> kvp in subParams)
806                        if (kvp.Value == null && target.Drive == null)
807                            target.Drive = Path.GetFullPath(kvp.Key);
808                        else if (kvp.Key == "clusterTips")
809                            target.EraseClusterTips = true;
810                        else
811                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
812                    targets.Add(target);
813                }
814                else if (IsParam(param, "dir", "d") || IsParam(param, "directory", null))
815                {
816                    if (equalPos == -1)
817                        throw new ArgumentException("--directory must be specified with the " +
818                            "directory to erase.");
819
820                    //Create the base target
821                    Task.Folder target = new Task.Folder();
822
823                    //Parse the subparameters.
824                    List<KeyValuePair<string, string>> subParams =
825                        GetSubParameters(param.Substring(equalPos + 1));
826                    foreach (KeyValuePair<string, string> kvp in subParams)
827                        if (kvp.Value == null && target.Path == null)
828                            target.Path = Path.GetFullPath(kvp.Key);
829                        else if (kvp.Key == "excludeMask")
830                        {
831                            if (kvp.Value == null)
832                                throw new ArgumentException("The exclude mask must be specified " +
833                                    "if the excludeMask subparameter is specified");
834                            target.ExcludeMask = kvp.Value;
835                        }
836                        else if (kvp.Key == "includeMask")
837                        {
838                            if (kvp.Value == null)
839                                throw new ArgumentException("The include mask must be specified " +
840                                    "if the includeMask subparameter is specified");
841                            target.IncludeMask = kvp.Value;
842                        }
843                        else if (kvp.Key == "delete")
844                            target.DeleteIfEmpty = true;
845                        else
846                            throw new ArgumentException("Unknown subparameter: " + kvp.Key);
847
848                    //Add the target to the list of targets
849                    targets.Add(target);
850                }
851                else
852                {
853                    //It's just a file!
854                    Task.File target = new Task.File();
855                    target.Path = Path.GetFullPath(param);
856                    targets.Add(target);
857                }
858
859                return true;
860            }
861
862            /// <summary>
863            /// The erasure method which the user specified on the command line.
864            /// </summary>
865            public Guid ErasureMethod
866            {
867                get
868                {
869                    return erasureMethod;
870                }
871                private set
872                {
873                    erasureMethod = value;
874                }
875            }
876
877            /// <summary>
878            /// The schedule for the current set of targets.
879            /// </summary>
880            public Schedule Schedule
881            {
882                get
883                {
884                    return schedule;
885                }
886                set
887                {
888                    schedule = value;
889                }
890            }
891
892            /// <summary>
893            /// The list of targets which was specified on the command line.
894            /// </summary>
895            public List<Task.ErasureTarget> Targets
896            {
897                get
898                {
899                    return new List<Task.ErasureTarget>(targets.ToArray());
900                }
901                set
902                {
903                    targets = value;
904                }
905            }
906
907            private Guid erasureMethod;
908            private Schedule schedule = Schedule.RunNow;
909            private List<Task.ErasureTarget> targets = new List<Task.ErasureTarget>();
910        }
911        #endregion
912
913        /// <summary>
914        /// Constructor.
915        /// </summary>
916        /// <param name="cmdParams">The raw command line arguments passed to the program.</param>
917        public CommandLineProgram(string[] cmdParams)
918        {
919            try
920            {
921                //Parse the command line arguments.
922                if (cmdParams.Length < 1)
923                    throw new ArgumentException("An action must be specified.");
924
925                switch (cmdParams[0])
926                {
927                    case "addtask":
928                        Arguments = new AddTaskCommandLine(cmdParams);
929                        break;
930                    case "querymethods":
931                    case "help":
932                    default:
933                        Arguments = new CommandLine(cmdParams);
934                        break;
935                }
936
937                //If the user did not specify the quiet command line, then create the console.
938                if (!Arguments.Quiet)
939                    CreateConsole();
940
941                //Map actions to their handlers
942                actionHandlers.Add("addtask", AddTask);
943                actionHandlers.Add("querymethods", QueryMethods);
944                actionHandlers.Add("help", Help);
945            }
946            finally
947            {
948                if (Arguments == null || !Arguments.Quiet)
949                    CreateConsole();
950            }
951        }
952
953        /// <summary>
954        /// Runs the program, analogous to System.Windows.Forms.Application.Run.
955        /// </summary>
956        public void Run()
957        {
958            //Call the function handling the current command line.
959            actionHandlers[Arguments.Action]();
960        }
961
962        /// <summary>
963        /// Creates a console for our application, setting the input/output streams to the
964        /// defaults.
965        /// </summary>
966        private void CreateConsole()
967        {
968            if (KernelAPI.AllocConsole())
969            {
970                Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
971                Console.SetIn(new StreamReader(Console.OpenStandardInput()));
972            }
973        }
974
975        /// <summary>
976        /// Prints the command line help for Eraser.
977        /// </summary>
978        private static void CommandUsage()
979        {
980            Console.WriteLine(@"usage: Eraser <action> <arguments>
981where action is
982    help                    Show this help message.
983    addtask                 Adds tasks to the current task list.
984    querymethods            Lists all registered Erasure methods.
985
986global parameters:
987    --quiet, -q             Do not create a Console window to display progress.
988
989parameters for help:
990    eraser help
991
992    no parameters to set.
993
994parameters for addtask:
995    eraser addtask [--method=<methodGUID>] [--schedule=(now|restart)] (--recycled " +
996@"| --unused=<volume> | --dir=<directory> | [file1 [file2 [...]]])
997
998    --method, -m            The Erasure method to use.
999    --schedule, -s          The schedule the task will follow. The value must
1000                            be one of:
1001            now             The task will be queued for immediate execution.
1002            restart         The task will be queued for execution when the
1003                            computer is next restarted.
1004    --recycled, -r          Erases files and folders in the recycle bin
1005    --unused, -u            Erases unused space in the volume.
1006        optional arguments: --unused=<drive>[,clusterTips]
1007            clusterTips     If specified, the drive's files will have their
1008                            cluster tips erased.
1009    --dir, --directory, -d  Erases files and folders in the directory
1010        optional arguments: --dir=<directory>[,e=excludeMask][,i=includeMask][,delete]
1011            excludeMask     A wildcard expression for files and folders to
1012                            exclude.
1013            includeMask     A wildcard expression for files and folders to
1014                            include.
1015                            The include mask is applied before the exclude
1016                            mask.
1017            delete          Deletes the folder at the end of the erasure if
1018                            specified.
1019    file1 ... fileN         The list of files to erase.
1020
1021parameters for querymethods:
1022    eraser querymethods
1023
1024    no parameters to set.
1025
1026All arguments are case sensitive.");
1027            Console.Out.Flush();
1028        }
1029
1030        #region Action Handlers
1031        /// <summary>
1032        /// The command line arguments passed to the program.
1033        /// </summary>
1034        public CommandLine Arguments
1035        {
1036            get
1037            {
1038                return arguments;
1039            }
1040            private set
1041            {
1042                arguments = value;
1043            }
1044        }
1045
1046        /// <summary>
1047        /// Prints the help text for Eraser (with copyright)
1048        /// </summary>
1049        private void Help()
1050        {
1051            Console.WriteLine(@"Eraser {0}
1052(c) 2008 The Eraser Project
1053Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
1054", Assembly.GetExecutingAssembly().GetName().Version);
1055
1056            Console.Out.Flush();
1057            CommandUsage();
1058        }
1059
1060        /// <summary>
1061        /// Lists all registered erasure methods.
1062        /// </summary>
1063        /// <param name="commandLine">The command line parameters passed to the program.</param>
1064        private void QueryMethods()
1065        {
1066            //Output the header
1067            const string methodFormat = "{0,-2} {1,-39} {2}";
1068            Console.WriteLine(methodFormat, "", "Method", "GUID");
1069            Console.WriteLine(new string('-', 79));
1070
1071            //Refresh the list of erasure methods
1072            Dictionary<Guid, ErasureMethod> methods = ErasureMethodManager.GetAll();
1073            foreach (ErasureMethod method in methods.Values)
1074            {
1075                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
1076                    "U" : "", method.Name, method.GUID.ToString());
1077            }
1078        }
1079
1080        /// <summary>
1081        /// Parses the command line for tasks and adds them using the
1082        /// <see cref="RemoteExecutor"/> class.
1083        /// </summary>
1084        /// <param name="commandLine">The command line parameters passed to the program.</param>
1085        private void AddTask()
1086        {
1087            AddTaskCommandLine arguments = (AddTaskCommandLine)Arguments;
1088           
1089            //Create the task, and set the method to use.
1090            Task task = new Task();
1091            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ? 
1092                ErasureMethodManager.Default :
1093                ErasureMethodManager.GetInstance(arguments.ErasureMethod);
1094            foreach (Task.ErasureTarget target in arguments.Targets)
1095            {
1096                target.Method = method;
1097                task.Targets.Add(target);
1098            }
1099
1100            //Check the number of tasks in the task.
1101            if (task.Targets.Count == 0)
1102                throw new ArgumentException("Tasks must contain at least one erasure target.");
1103
1104            //Set the schedule for the task.
1105            task.Schedule = arguments.Schedule;
1106
1107            //Send the task out.
1108            try
1109            {
1110                using (Program.eraserClient = new RemoteExecutorClient())
1111                {
1112                    if (!((RemoteExecutorClient)Program.eraserClient).Connect())
1113                    {
1114                        //The client cannot connect to the server. This probably means
1115                        //that the server process isn't running. Start an instance.
1116                        Process eraserInstance = Process.Start(
1117                            Assembly.GetExecutingAssembly().Location, "--quiet");
1118                        eraserInstance.WaitForInputIdle();
1119
1120                        if (!((RemoteExecutorClient)Program.eraserClient).Connect())
1121                            throw new Exception("Eraser cannot connect to the running " +
1122                                "instance for erasures.");
1123                    }
1124
1125                    Program.eraserClient.Run();
1126                    Program.eraserClient.AddTask(ref task);
1127                }
1128            }
1129            catch (UnauthorizedAccessException e)
1130            {
1131                //We can't connect to the pipe because the other instance of Eraser
1132                //is running with higher privileges than this instance.
1133                throw new UnauthorizedAccessException("Another instance of Eraser " +
1134                    "is already running but it is running with higher privileges than " +
1135                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
1136                    "Close the running instance of Eraser and start it again without " +
1137                    "administrator privileges, or run the command again as an administrator",
1138                    e);
1139            }
1140        }
1141        #endregion
1142
1143        /// <see cref="Arguments"/>
1144        private CommandLine arguments;
1145
1146        /// <summary>
1147        /// The prototype of an action handler in the class which executes an
1148        /// action as specified in the command line.
1149        /// </summary>
1150        private delegate void ActionHandler();
1151
1152        /// <summary>
1153        /// Matches an action handler to a function in the class.
1154        /// </summary>
1155        private Dictionary<string, ActionHandler> actionHandlers =
1156            new Dictionary<string, ActionHandler>();
1157    }
1158
1159    internal class Settings : Manager.SettingsManager
1160    {
1161        /// <summary>
1162        /// Registry-based storage backing for the Settings class.
1163        /// </summary>
1164        private class RegistrySettings : Manager.Settings
1165        {
1166            /// <summary>
1167            /// Constructor.
1168            /// </summary>
1169            /// <param name="key">The registry key to look for the settings in.</param>
1170            public RegistrySettings(Guid pluginID, RegistryKey key)
1171            {
1172                this.key = key;
1173            }
1174
1175            public override object this[string setting]
1176            {
1177                get
1178                {
1179                    byte[] currentSetting = (byte[])key.GetValue(setting, null);
1180                    if (currentSetting != null && currentSetting.Length != 0)
1181                        using (MemoryStream stream = new MemoryStream(currentSetting))
1182                            try
1183                            {
1184                                return new BinaryFormatter().Deserialize(stream);
1185                            }
1186                            catch (Exception)
1187                            {
1188                                key.DeleteValue(setting);
1189                                MessageBox.Show(S._("Could not load the setting {0} for plugin {1}. " +
1190                                    "The setting has been lost.", key, pluginID.ToString()),
1191                                    S._("Eraser"), MessageBoxButtons.OK, MessageBoxIcon.Error);
1192                            }
1193
1194                    return null;
1195                }
1196                set
1197                {
1198                    if (value == null)
1199                    {
1200                        key.DeleteValue(setting);
1201                    }
1202                    else
1203                    {
1204                        using (MemoryStream stream = new MemoryStream())
1205                        {
1206                            new BinaryFormatter().Serialize(stream, value);
1207                            key.SetValue(setting, stream.ToArray(), RegistryValueKind.Binary);
1208                        }
1209                    }
1210                }
1211            }
1212
1213            /// <summary>
1214            /// The GUID of the plugin whose settings this object is storing.
1215            /// </summary>
1216            private Guid pluginID;
1217
1218            /// <summary>
1219            /// The registry key where the data is stored.
1220            /// </summary>
1221            private RegistryKey key;
1222        }
1223
1224        public override void Save()
1225        {
1226        }
1227
1228        protected override Manager.Settings GetSettings(Guid guid)
1229        {
1230            //Open the registry key containing the settings
1231            const string eraserKeyPath = @"SOFTWARE\Eraser\Eraser 6";
1232            RegistryKey eraserKey = Registry.CurrentUser.OpenSubKey(eraserKeyPath, true);
1233            if (eraserKey == null)
1234                eraserKey = Registry.CurrentUser.CreateSubKey(eraserKeyPath);
1235
1236            RegistryKey pluginsKey = eraserKey.OpenSubKey(guid.ToString(), true);
1237            if (pluginsKey == null)
1238                pluginsKey = eraserKey.CreateSubKey(guid.ToString());
1239
1240            //Return the Settings object.
1241            return new RegistrySettings(guid, pluginsKey);
1242        }
1243    }
1244
1245    internal class EraserSettings
1246    {
1247        public EraserSettings()
1248        {
1249            settings = Manager.ManagerLibrary.Instance.SettingsManager.ModuleSettings;
1250        }
1251
1252        /// <summary>
1253        /// Gets or sets the task list, serialised in binary form by the Manager assembly.
1254        /// </summary>
1255        public byte[] TaskList
1256        {
1257            get
1258            {
1259                return (byte[])settings["TaskList"];
1260            }
1261            set
1262            {
1263                settings["TaskList"] = value;
1264            }
1265        }
1266
1267        /// <summary>
1268        /// Gets or sets the LCID of the language which the UI should be displayed in.
1269        /// </summary>
1270        public string Language
1271        {
1272            get
1273            {
1274                return settings["Language"] == null ?
1275                    GetCurrentCulture().Name :
1276                    (string)settings["Language"];
1277            }
1278            set
1279            {
1280                settings["Language"] = value;
1281            }
1282        }
1283
1284        /// <summary>
1285        /// Gets or sets whether the Shell Extension should be loaded into Explorer.
1286        /// </summary>
1287        public bool IntegrateWithShell
1288        {
1289            get
1290            {
1291                return settings["IntegrateWithShell"] == null ?
1292                    true : (bool)settings["IntegrateWithShell"];
1293            }
1294            set
1295            {
1296                settings["IntegrateWithShell"] = value;
1297            }
1298        }
1299
1300        /// <summary>
1301        /// Gets or sets a value on whether the main frame should be minimised to the
1302        /// system notification area.
1303        /// </summary>
1304        public bool HideWhenMinimised
1305        {
1306            get
1307            {
1308                return settings["HideWhenMinimised"] == null ?
1309                    true : (bool)settings["HideWhenMinimised"];
1310            }
1311            set
1312            {
1313                settings["HideWhenMinimised"] = value;
1314            }
1315        }
1316
1317        /// <summary>
1318        /// Gets the current UI culture, correct to the top-level culture (i.e., English
1319        /// instead of English (United Kingdom))
1320        /// </summary>
1321        /// <returns>The CultureInfo of the current UI culture, correct to the top level.</returns>
1322        private static CultureInfo GetCurrentCulture()
1323        {
1324            CultureInfo culture = CultureInfo.CurrentUICulture;
1325            while (culture.Parent != CultureInfo.InvariantCulture)
1326                culture = culture.Parent;
1327
1328            return culture;
1329        }
1330
1331        private Manager.Settings settings;
1332    }
1333}
Note: See TracBrowser for help on using the repository browser.