Friday, October 31, 2008

kopier, a multi-threaded backup utility in C#

Kopier manages a pool of threads, each thread managing an instance of Robocopy. Some logging and error handling is built in to her as well.

the program class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Mail;

namespace kopier
{
    class Program
    {
        const int maxThreads = 8;

        static void Main(string[] args)
        {
            DateTime startTime;
            int w, c;
            StringBuilder aggMsg = new StringBuilder();

            string[] lines = System.IO.File.ReadAllLines(@args[0]);

            ManualResetEvent[] doneEvents = new ManualResetEvent[lines.Count()];
            kopy[] kopArray = new kopy[lines.Count()];

            ThreadPool.SetMaxThreads(maxThreads, 100);
            ThreadPool.GetMaxThreads(out w, out c);

            Console.WriteLine("--------------------------------------------------------------------------------");
            Console.WriteLine("kopier");
            Console.WriteLine("--------------------------------------------------------------------------------");
            Console.WriteLine("Max threads: " + w + ", " + c);
            Console.WriteLine("Launching {0} jobs...", lines.Count());
            startTime = DateTime.Now;
           
            for (int i = 0; i < lines.Count(); i++)
            {
                doneEvents[i] = new ManualResetEvent(false);
                string[] switches = lines[i].Split(new char[]{','});
                string src = switches[0];
                string dst = switches[1];
                TimeSpan min = TimeSpan.FromMinutes(double.Parse(switches[2]));
                kopy k = new kopy(src, dst, min, doneEvents[i]);
                kopArray[i] = k;
                ThreadPool.QueueUserWorkItem(k.threadPoolCallback, i);
            }

            WaitHandle.WaitAll(doneEvents);
            Console.WriteLine("All jobs are complete.");

            aggMsg.Append("started: " + startTime.ToShortTimeString() + ", " + startTime.ToShortDateString() + "\n");
            aggMsg.Append("finished: " + DateTime.Now.ToShortTimeString() + ", " + DateTime.Now.ToShortDateString() + "\n");
            aggMsg.Append("duration: " + DateTime.Now.Subtract(startTime).ToString() + "\r \n");

            for (int i = 0; i < lines.Count(); i++)
            {
                kopy k = kopArray[i];
                Console.WriteLine("job({0}): {1}", k.srcDir, k.kopyMsg);
                aggMsg.Append("job(" + k.srcDir + "): " + k.kopyMsg + "\r \n");
            }

            sendMsg(aggMsg.ToString());
        }

        private static void sendMsg(string msg)
        {
            try
            {
                MailMessage email = new MailMessage();
                email.From = new MailAddress("results@kopy.int");
                email.To.Add(new MailAddress("elijahb@bargain.com"));
                email.Subject = "kopy results";
                email.Body = msg;
                SmtpClient client = new SmtpClient("172.16.12.25");
                client.Send(email);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception during email: {0}", e);
            }
        }
    }
}


the kopy class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using System.Diagnostics;

namespace kopier
{
    class kopy
    {
        public kopy(string srcDir, string dstDir, TimeSpan timeMin, ManualResetEvent doneEvent)
        {
            _srcDir = srcDir;
            _dstDir = dstDir;
            _timeMin = timeMin;
            _doneEvent = doneEvent;
        }

        public void threadPoolCallback(object threadContext)
        {
            int threadIndex = (int)threadContext;
            _startTime = DateTime.Now;
 
            Console.Write("job {0} started..." +
                "\n \t job {0} source = " + _srcDir +
                "\n \t job {0} destination = " + _dstDir +
                "\n \t job {0} time limit = " + _timeMin.TotalMinutes.ToString() + " minutes" +
                "\n", threadIndex);
 
            _kopyMsg = kopyAway(_srcDir, _dstDir, _timeMin);

            Console.Write("job {0} finished..." +
                "\n \t job {0} source = " + _srcDir +
                "\n \t job {0} destination = " + _dstDir +
                "\n \t job {0} duration = " + DateTime.Now.Subtract(_startTime).ToString() +
                "\n", threadIndex);

            _doneEvent.Set();
        }

        private string kopyAway(string src, string dst, TimeSpan min)
        {
            string log = dst + @"\rcLog(" + DateTime.Today.ToShortDateString().Replace('/', '-') + ").txt";
            string msg;
            Process p = new Process();

            if (System.IO.Directory.Exists(dst) != true)
            {
                System.IO.Directory.CreateDirectory(dst);
            }

            dst = dst + @"\files";

            try
            {
                p.StartInfo.FileName = "ROBOCOPY";
                p.StartInfo.Arguments = '"' + src + '"' + " " + '"' + dst + '"' + " /MIR /W:10 /R:3 /NP /TEE /LOG+:" + '"' + log + '"';
                p.Start();

                do
                {
                    if (DateTime.Now.Subtract(p.StartTime) >= min)
                    {
                        p.Kill();
                        throw new Exception(buildOutput(src, dst, p.StartTime, DateTime.Now, "Failed: killed for exceeding max time."));
                    }
                    System.Threading.Thread.Sleep(500);
                } while (p.HasExited == false);

                msg = buildOutput(src, dst, p.StartTime, DateTime.Now, "Succeeded.");
            }
            catch (Exception e)
            {
                msg = e.Message.ToString();
            }
            finally
            {
                p.Dispose();
            }
            return msg;
        }

        private string buildOutput(string src, string dst, DateTime startTime, DateTime endTime, string result)
        {  
            return result +
                "\n \t source: " + src +
                "\n \t destination: " + dst +
                "\n \t started: " + startTime.ToShortTimeString() + ", " + startTime.ToShortDateString() +
                "\n \t finished: " + endTime.ToShortTimeString() + ", " + endTime.ToShortDateString() +
                "\n \t duration: " + endTime.Subtract(startTime).ToString();
        }

        public string srcDir { get { return _srcDir; } }
        private string _srcDir;

        public string dstDir { get { return _dstDir; } }
        private string _dstDir;

        public TimeSpan timeMin { get { return _timeMin; } }
        private TimeSpan _timeMin;

        public string kopyMsg { get { return _kopyMsg; } }
        private string _kopyMsg;

        private ManualResetEvent _doneEvent;

        private DateTime _startTime;
    }
}

Thursday, October 23, 2008

setting up IPSec between Windows 2003 and 2000

Following is a procedure to establish IP Security (IPSec) between two Windows Server hosts, one running Server 2003 and the other running 2000 Server. The 2000 Server host is configured to match the more robust default security parameters of Server 2003, while the Server 2003 host is left mostly in a default state. SHA1 is the specified integrity algorithm while packet data is encrypted using 3DES. Both servers require all IP communication between them, but only them, be authentic and encrypted. Traffic from all other sources is not affected by IPSec.

Part I - Configure Windows 2000 Server

1. Create a Console
start > in the run line, type 'mmc' >
ipsec-89
OK >
ipsec-990
In the Console1 window, click 'Console' > Add/Remove Snap-in... >
ipsec-991
Add... > select 'IP Security Policy Management' >
ipsec-992
Add > check next to 'Local Computer' >
ipsec-993
Finish >
ipsec-994
Close >
ipsec-996
OK >
ipsec-01
2. Create a New IP Security Policy
right click on 'IP Security Policies on Local Machine' > Create IP Security Policy >
ipsec-02
Next >
ipsec-03
Next > uncheck 'Activate the default response rule.' >
ipsec-03b
Next > check 'Edit properties' >
ipsec-03c
Finish > check 'Use Add Wizard' >
ipsec-04
Add... >
ipsec-05
Next > check 'This rule does not specify a tunnel' >
ipsec-05a
Next > check 'All network connections' >
ipsec-05b
Next > check 'Use this string to protect the key exchange (preshared key):' > choose a password and type it in the following field >
ipsec-06
Next >
ipsec-05c
3. Create a New IP Filter List for this Security Policy
Add... >
ipsec-07
check 'Use Add Wizard' > Add... >
ipsec-08
Next > select 'My IP Address' from 'Source address:' list >
ipsec-07a
Next > select 'A specific IP Address' from 'Destination address:' list > type SERVER1's IP address in the following field >
ipsec-09
Next > select 'Any' from 'Select a protocol type:' list >
ipsec-08a
Next > uncheck 'Edit Properties' >
ipsec-08b
Finish >
ipsec-08c
Close > select the 'IP Filter List' you just made >
ipsec-09
Next > check 'Use Add Wizard' >
ipsec-09a
4. Create a New Filter Action for this Security Policy
Add... >
ipsec-11
Next >
ipsec-12
Next > check 'Negotiate security' >
ipsec-11a
Next > check 'Do not communicate with computers that do not support IPSec.' >
ipsec-11b
Next > check 'Custom' > Settings... >
ipsec-11c
check 'Data integrity and encryption (ESP):' > select 'SHA1' from 'Integrity algorithm:' list > select '3DES' from 'Encryption algorithm:' list >
ipsec-13
OK >
ipsec-12a
Next >
ipsec-12b
Finish > select the Filter Action you just made >
ipsec-12c
Next > uncheck 'Edit Properties' >
ipsec-12d
Finish >
ipsec-12e
Close >
ipsec-887
right click the IP Security Policy you just made > Assign >
ipsec-888
...and you're done.
5. Test the Configuration
If you're pinging SERVER1 when the security association comes up, you'll see this:
ipsec-90
However, you won't see these replies until both ends are configured. The pings will change from 'Negotiating IP Security' to 'Reply from ...' only after finishing the configuration on SERVER1, the Server 2003 host described in the next section of this document.
Use the built-in IP Security Monitor to verify a successful security association.
Start > type 'ipsecmon'  >
ipsec-995
OK >
ipsec-91
This association will appear only after both ends are configured, both the Windows 2000 Server host discussed above and the Server 2003 host described below.

Part II - Configure Windows Server 2003

1. Create a Console
Start > Run > type 'mmc' >
ipsec-01
OK >
ipsec-02
In the Console1 window, click 'File' > Add/Remove Snap-in... >
ipsec-03
Add... > select 'IP Security Monitor' >
ipsec-04
Add > select 'IP Security Policy Management' >
ipsec-05
Add > select 'Local Computer' >
ipsec-06
Finish >
ipsec-07
Close >
ipsec-08
OK >
ipsec-09
2. Create a New IP Security Policy
right click on 'IP Security Policies on Local Machine' > select 'Create IP Security Policy' >
ipsec-10
Next >
ipsec-11
Next > uncheck 'Activate the default response rule.' >
ipsec-12
Next > check 'Edit properties' >
ipsec-13
Finish > check 'Use Add Wizard' >
ipsec-14
Add... >
ipsec-15
Next > check 'This rule does not specify a tunnel' >
ipsec-16
Next > check 'All network connections' >
ipsec-17
Next >
ipsec-18
3. Create a New IP Filter List for this Security Policy.
Add... > check 'Use Add Wizard' >
ipsec-19
Add... >
ipsec-20
Next > check 'Mirrored. Match packets with the exact opposite source and destination addresses.' >
ipsec-21
Next > select 'My IP Address' from 'Source address:' list >
ipsec-22
Next > select 'A specific IP Address' from 'Destination address:' list > type SERVER2's IP address in the following field >
ipsec-23
Next > select 'Any' from 'Select a protocol type:' list >
ipsec-24
Next > uncheck 'Edit properties' >
ipsec-25
Finish >
ipsec-26
OK > select the 'IP Filter List' you just made >
ipsec-27
Next > select 'Use Add Wizard' >
ipsec-28
4. Create a New Filter Action for this Security Policy
Add... >
ipsec-29
Next >
ipsec-30
Next > check 'Negotiate security' >
ipsec-31
Next > check 'Do not communicate with computers that do not support IPSec.' >
ipsec-32
Next > check 'Custom' >
ipsec-33
Settings... > check 'Data integrity and encryption (ESP):' > select 'SHA1' from 'Integrity algorithm:' list > select '3DES' from Encryption algorithm:' list >
ipsec-34
OK >
ipsec-35
OK >
ipsec-36
Next > uncheck 'Edit properties' >
ipsec-37
Finish > select the Filter Action you just made >
ipsec-38
Next > select 'Use this string to protect the key exchange (preshared key):' > choose a password and type it in the following field >
ipsec-39
Next > uncheck 'Edit properties' >
ipsec-40
Finish >
ipsec-41
OK >
ipsec-42
right click the IP Security Policy you just made > Assign >
ipsec-43
...and you're done.
5. Test the Configuration
You already set up SERVER2 so completing this configuration on SERVER1 will complete both ends of the security association. If you are pinging SERVER2 from SERVER1 as this association comes up, you will see this:
ipsec-90
A successful association should be indicated under the 'IP Security Monitor' node.
ipsec-91
see also: