using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;

namespace Disco.ClientBootstrapper.Interop.WIMInterop
{
    ///<summary>
    ///Pulic interface to a WindowsImage object.
    ///</summary>
    public
    interface IImage : IDisposable
    {
        ///<summary>
        ///Gets image information from within a .wim file.
        ///</summary>
        string ImageInformation
        {
            get;
        }

        ///<summary>
        ///Sets image information about an image within a .wim file.
        ///</summary>
        ///<param name="imageInformation">The string being passed in should be in the form of a unicode XML file.
        ///Calling this function replaces any customized image data. To preserve existing XML information, call ImageInformation
        ///and append/edit desired data.
        ///</param>
        void SetImageInformation(
            string imageInformation
        );

        ///<summary>
        ///Mounts an image in a .wim file to the specified directory.
        ///</summary>
        void Mount(
            string pathToMountTo
        );

        ///<summary>
        ///Retrieves the path to which an image has been mounted.
        ///</summary>
        string MountedPath
        {
            get;
        }

        ///<summary>
        ///Unmounts a mounted image in a .wim file from the specified directory.
        ///</summary>
        ///<param name="commitChanges">Indicates whether changes (if any) to the .wim file should be committed 
        ///before unmounting the .wim file. This flag will have no effect if the .wim file was mounted not to allow edits.
        ///</param>
        void DismountImage(
            bool commitChanges
        );

        ///<summary>
        ///Applies an image to a drive root or to a directory path from a .wim file.
        ///</summary>
        void Apply(
            string pathToApplyTo
        );
    }

    ///<summary>
    ///Class representing a .wim file.
    ///</summary>
    public
    sealed
    class
    WindowsImageContainer : IDisposable
    {
        ///<summary>
        ///Specifies the type of access to the .wim file.
        ///</summary>
        public
        enum
        CreateFileAccess
        {
            ///<summary>
            ///Specifies read-only access to the .wim file.
            ///</summary>
            Read,

            ///<summary>
            ///Specifies write access to the .wim file.
            ///Includes WIM_GENERIC_READ access to enable apply and append operations with existing images.
            ///</summary>
            Write
        }

        ///<summary>
        ///Specifies which action to take on files that exist and 
        ///which action to take when files do not exist.
        ///</summary>
        public
        enum
        CreateFileMode
        {
            ///<summary>
            ///RESERVED, DO NOT USE!
            ///</summary>
            None = 0,

            ///<summary>
            ///Creates a new .wim file. The function fails if the specified file already exists.
            ///</summary>
            CreateNew = 1,

            ///<summary>
            ///Creates a new .wim file. If the file exists, the function overwrites the file.
            ///</summary>
            CreateAlways = 2,

            ///<summary>
            ///Opens the .wim file. The function fails if the file does not exist.
            ///</summary>
            OpenExisting = 3,

            ///<summary>
            ///Opens the .wim file if it exists. If the file does not exist and the caller requests WIM_GENERIC_WRITE access, the 
            ///function creates the file.
            ///</summary>
            OpenAlways = 4
        }

        ///<summary>
        ///Public constructor to create a WIM object
        ///</summary>
        ///<param name="imageFilePath">Path of WIM to create or to open.</param>
        ///<param name="mode">Specifies Open, Create, Create/Open disposition of the .wim file.</param>
        ///<param name="access">Specifies access level of Read Only or Write.</param>
        //[CLSCompliant(false)]
        public WindowsImageContainer(string imageFilePath, CreateFileMode mode, CreateFileAccess access)
        {
            CreateFileAccessPrivate fileAccess = GetMappedFileAccess(access);
            if (fileAccess == CreateFileAccessPrivate.Read && (!File.Exists(imageFilePath) || (CreateFileMode.OpenExisting != mode)))
            {
                throw new System.UnauthorizedAccessException(string.Format(CultureInfo.CurrentCulture,
                                 "Read access can be specified only with OpenExisting mode or OpenAlways mode when the .wim file does not exist."));
            }

            //
            //Imaging DLLs must be in the same directory.
            //
            try
            {
                m_ImageContainerHandle = NativeMethods.CreateFile(imageFilePath, (uint)fileAccess, (uint)mode);
                m_WindowsImageFilePath = imageFilePath;
            }
            catch (System.DllNotFoundException ex)
            {
                throw new System.DllNotFoundException(string.Format(CultureInfo.CurrentCulture,
                                  "Unable to load WIM libraries. Make sure the correct DLLs are present (Wimgapi.dll and Xmlrw.dll)."), ex.InnerException);
            }

            if (!m_ImageContainerHandle.Equals(IntPtr.Zero))
            {
                //
                //Set the temporary path so that we can write to an image. This
                //cannot be %TEMP% as it does not exist on Windows PE
                //
                string tempDirectory = System.Environment.GetEnvironmentVariable("systemdrive");
                NativeMethods.SetTemporaryPath(m_ImageContainerHandle, tempDirectory);

            }
            else
            {
                //
                //Throw an exception
                //
                throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                         "Unable to open  the .wim file {0}.", imageFilePath));
            }

            //
            //Finally, we must hook into the events.
            //
            m_MessageCallback = new NativeMethods.MessageCallback(ImageEventMessagePump);
            NativeMethods.RegisterCallback(m_MessageCallback);
        }

        ///<summary> Destructor to close open handles.</summary>
        ~WindowsImageContainer()
        {
            DisposeInner();
        }

        ///<summary>
        ///Dispose all unmanaged resources
        ///</summary>
        public
        void
        Dispose()
        {
            DisposeInner();
            GC.SuppressFinalize(this);
        }

        private
        void
        DisposeInner()
        {
            if (m_ImageContainerHandle != IntPtr.Zero)
            {
                NativeMethods.CloseHandle(m_ImageContainerHandle);
                m_ImageContainerHandle = IntPtr.Zero;
            }

            if (m_MessageCallback != null)
            {
                NativeMethods.UnregisterMessageCallback(m_MessageCallback);
                m_MessageCallback = null;
            }
            GC.KeepAlive(this);
        }

        ///<summary>
        ///Used to enumerate the WindowsImage array
        ///</summary>
        public
        IEnumerator
        GetEnumerator(
            )
        {
            return m_Images.GetEnumerator();
        }

        ///<summary>
        ///[] overload, used to enumerate the WindowsImage array
        ///</summary>
        public
        IImage
        this[int imageIndex]
        {
            get
            {
                //
                //Delay load images
                //
                if (m_Images == null || m_Images[imageIndex] == null)
                {

                    ArrayList tempImages = new ArrayList();
                    tempImages.Add(new WindowsImage(m_ImageContainerHandle, m_WindowsImageFilePath, imageIndex + 1));
                    m_Images = (WindowsImage[])tempImages.ToArray(typeof(WindowsImage));
                }
                GC.KeepAlive(this);
                return m_Images[imageIndex];
            }
        }

        ///<summary>
        ///Retrieve the number of images in the .wim file.
        ///</summary>
        public int ImageCount
        {
            get
            {
                //
                //Verify if there is an image count; if not, get it.
                //
                if (m_ImageCount == 0)
                {
                    m_ImageCount = NativeMethods.GetImageCount(m_ImageContainerHandle);
                }

                GC.KeepAlive(this);
                return m_ImageCount;
            }
        }

        ///<summary>
        ///Capture an image from the root of a drive or from an individual directory.
        ///</summary>
        public void CaptureImage(string pathToCapture)
        {
            //
            //Capture the image.
            //
            IntPtr windowsImageHandle = NativeMethods.CaptureImage(m_ImageContainerHandle, pathToCapture);
            NativeMethods.CloseHandle(windowsImageHandle);
            GC.KeepAlive(this);
        }

        ///<summary>
        ///Default event handler
        ///</summary>
        //[CLSCompliant(false)]
        public delegate void DefaultImageEventHandler(object sender, DefaultImageEventArgs e);
        //public delegate void DefaultImageEventHandler(IntPtr wParam, IntPtr lParam, IntPtr UserData);
        ///<summary>
        ///ProcessFileEvent handler
        ///</summary>
        //[CLSCompliant(false)]
        public delegate void ProcessFileEventHandler(object sender, ProcessFileEventArgs e);
        //public delegate void ProcessFileEventHandler(ProcessFile fileToProcess);


        ///<summary>
        ///Indicate an update in the progress of an image application.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler ProgressEvent;
        ///<summary>
        ///Enable the caller to prevent a file or a directory from being captured or applied.
        ///</summary>
        //[CLSCompliant(false)]
        public event ProcessFileEventHandler ProcessFileEvent;
        ///<summary>
        ///Enable the caller to prevent a file resource from being compressed during a capture.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler CompressEvent;
        ///<summary>
        ///Alert the caller that an error has occurred while capturing or applying an image.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler ErrorEvent;
        ///<summary>
        ///Enable the caller to align a file resource on a particular alignment boundary.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler AlignmentEvent;
        ///<summary>
        ///Enable the caller to align a file resource on a particular alignment boundary.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler SplitEvent;
        ///<summary>
        ///Indicate that volume information is being gathered during an image capture.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler ScanningEvent;
        ///<summary>
        ///Indicate the number of files that will be captured or applied.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler SetRangeEvent;
        ///<summary>
        ///Indicate the number of files that have been captured or applied.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler SetPosEvent;
        ///<summary>
        ///Indicate that a file has been either captured or applied.
        ///</summary>
        //[CLSCompliant(false)]
        public event DefaultImageEventHandler StepItEvent;


        ///<summary>
        ///Event callback to the Wimgapi events
        ///</summary>
        private
        uint
        ImageEventMessagePump(
            uint MessageId,
            IntPtr wParam,
            IntPtr lParam,
            IntPtr UserData
        )
        {
            uint status = (uint)NativeMethods.WIMMessage.WIM_MSG_SUCCESS;
            DefaultImageEventArgs eventArgs = new DefaultImageEventArgs(wParam, lParam, UserData);

            switch ((ImageEventMessage)MessageId)
            {

                case ImageEventMessage.Progress:
                    ProgressEvent(this, eventArgs);
                    break;

                case ImageEventMessage.Process:
                    string fileToImage = Marshal.PtrToStringUni(wParam);
                    ProcessFileEventArgs fileToProcess = new ProcessFileEventArgs(fileToImage, lParam);
                    ProcessFileEvent(this, fileToProcess);
                    if (fileToProcess.Abort == true)
                    {
                        status = (uint)ImageEventMessage.Abort;
                    }
                    break;

                case ImageEventMessage.Compress:
                    CompressEvent(this, eventArgs);
                    break;

                case ImageEventMessage.Error:
                    ErrorEvent(this, eventArgs);
                    break;

                case ImageEventMessage.Alignment:
                    AlignmentEvent(this, eventArgs);
                    break;

                case ImageEventMessage.Split:
                    SplitEvent(this, eventArgs);
                    break;

                case ImageEventMessage.Scanning:
                    ScanningEvent(this, eventArgs);
                    break;

                case ImageEventMessage.SetRange:
                    SetRangeEvent(this, eventArgs);
                    break;

                case ImageEventMessage.SetPos:
                    SetPosEvent(this, eventArgs);
                    break;

                case ImageEventMessage.StepIt:
                    StepItEvent(this, eventArgs);
                    break;

                default:
                    break;
            }

            return status;
        }

        ///<summary>
        ///Image inside of a .wim file
        ///</summary>
        private
        class
        WindowsImage : IImage
        {
            ///<summary>
            ///Public constructor to create an image object from inside a .wim file
            ///</summary>
            public WindowsImage(IntPtr imageContainerHandle, string imageContainerFilePath, int imageIndex)
            {
                m_ParentWindowsImageHandle = imageContainerHandle;
                m_ParentWindowsImageFilePath = imageContainerFilePath;
                m_Index = imageIndex;

                //
                //Load the image and stash away the handle.
                //
                m_ImageHandle = NativeMethods.LoadImage(imageContainerHandle, imageIndex);
            }

            ///<summary> Destructor to close open handles.</summary>
            ~WindowsImage()
            {
                DisposeInner();
            }

            ///<summary>
            ///Dispose all unmanaged resources.
            ///</summary>
            public
            void
            Dispose()
            {
                DisposeInner();
                GC.SuppressFinalize(this);
            }

            private
            void
            DisposeInner()
            {
                //
                //Do not leave any open handles or mounted images.
                //
                if (m_ImageHandle != IntPtr.Zero)
                {
                    NativeMethods.CloseHandle(m_ImageHandle);
                    m_ImageHandle = IntPtr.Zero;
                }

                if (m_Mounted == true)
                {
                    //
                    //Never commit changes when destroying this object.
                    //
                    this.DismountImage(false);
                }
                GC.KeepAlive(this);
            }

            ///<summary>
            ///Gets an image information header.
            ///</summary>
            ///<value></value>
            public string ImageInformation
            {
                get
                {
                    //
                    //Always get the image header (even if we have it already), removing the unicode file marker.
                    //
                    string str = NativeMethods.GetImageInformation(m_ImageHandle).Remove(0, 1);
                    GC.KeepAlive(this);

                    return str;
                }
            }

            //            ///<summary>
            //            ///Returns an XmlTextReader for a given node name.
            //            ///</summary>
            //            private XmlTextReader ImageInformationHelper(string raw) {

            //                XmlTextReader xmlTextReader = null;

            //                if (raw != null) {                    
            //                    XmlDocument xmlDocument = new XmlDocument();
            //                    try {
            //                        xmlDocument.LoadXml(raw);

            //                        //
            //                        //Look at all nodes.
            //                        //
            //                        foreach(XmlNode node in xmlDocument.ChildNodes) {

            //                            //
            //                            //Now, find the specified node
            //                            //
            //                            foreach(XmlNode childNode in node) {
            //                                if 
            //(String.Equals(childNode.Name, node.ToString(), StringComparison.InvariantCultureIgnoreCase)) {

            //                                    StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
            //                                    XmlTextWriter xmlTextWriter = new XmlTextWriter(stringWriter);

            //                                    xmlTextWriter.WriteStartElement(childNode.Name);                                    
            //                                    //
            //                                    //We are in the specified node. Now, get all the attributes
            //                                    //
            //                                    foreach(XmlAttribute attribute in childNode.Attributes) {
            //                                        xmlTextWriter.WriteAttributeString(attribute.Name, attribute.InnerXml);
            //                                    }
            //                                    xmlTextWriter.WriteEndElement();

            //                                    xmlTextReader = new XmlTextReader(null, new StringReader(stringWriter.ToString()));
            //                                }
            //                            }
            //                        }
            //                    }
            //                    catch (System.Xml.XmlException) {
            //                        throw new System.Xml.XmlException(string.Format(CultureInfo.CurrentCulture,
            //                                                                        "Unable to read XML header information from {0}", 
            //                                                                        this.m_ParentWindowsImageFilePath));
            //                    }
            //                }
            //                return xmlTextReader;
            //            }

            ///<summary>
            ///Retrieves the path to which an image has been mounted.
            /// </summary>
            public string MountedPath
            {
                get
                {
                    return (m_MountedPath != null) ? m_MountedPath : null;
                }
            }

            ///<summary>
            ///Sets the image information header
            ///</summary>
            ///<returns></returns>
            public void SetImageInformation(string imageInformation)
            {
                //
                //Format the incoming XML so that we can set the header. The XML must have:
                //1. Leading 0xFEFF
                //2. Be in <IMAGE></IMAGE>
                //
                string formattedXml = String.Format(CultureInfo.InvariantCulture,
                                                    "{0}{1}{2}{3}",
                                                    UNICODE_FILE_MARKER,
                                                    "<IMAGE>",
                                                    imageInformation,
                                                    "</IMAGE>");

                NativeMethods.SetImageInformation(m_ImageHandle, formattedXml);
                GC.KeepAlive(this);
            }

            ///<summary>
            ///Mounts an image to a directory.
            ///</summary>
            ///<returns></returns>
            public void Mount(string pathToMountTo)
            {
                //
                //Mount the image
                //
                m_MountedPath = pathToMountTo;
                NativeMethods.MountImage(pathToMountTo, m_ParentWindowsImageFilePath, m_Index, System.IO.Path.GetTempPath());
                m_Mounted = true;
            }

            ///<summary>
            ///Unmounts an image from a directory.
            ///</summary>
            ///<returns></returns>
            public void DismountImage(bool commitChanges)
            {
                if (m_Mounted == true)
                {
                    NativeMethods.DismountImage(m_MountedPath, m_ParentWindowsImageFilePath, m_Index, commitChanges);
                }
            }

            ///<summary>
            ///Applies an image to a drive root or to a directory path.
            ///</summary>
            ///<returns></returns>
            public void Apply(string pathToApplyTo)
            {
                NativeMethods.ApplyImage(m_ImageHandle, pathToApplyTo);
                GC.KeepAlive(this);
            }

            private IntPtr m_ParentWindowsImageHandle = IntPtr.Zero;    //.wim file handle
            private string m_ParentWindowsImageFilePath;                //path to .wim file

            private IntPtr m_ImageHandle = IntPtr.Zero;                 //image handle
            private int m_Index;                                        //index of image

            private string m_MountedPath;                               //where the image has been mounted
            private bool m_Mounted;                                     //true if image has been mounted

            //
            //DO NOT CHANGE! This controls the format of the image header
            //and it must be present.
            //
            private const string UNICODE_FILE_MARKER = "\uFEFF";
        }

        ///<summary>
        ///Interop to Wimgapi.dll
        ///</summary>
        public
        class
        NativeMethods
        {
            ///<summary>
            ///Private null constructor
            ///</summary>
            private
            NativeMethods() { }

            [DllImport("Wimgapi.dll", ExactSpelling = true,
                       EntryPoint = "WIMCreateFile",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            IntPtr
            WimCreateFile(
                [MarshalAs(UnmanagedType.LPWStr)] string WimPath,
                uint DesiredAccess,
                uint CreationDisposition,
                uint FlagsAndAttributes,
                uint CompressionType,
                out IntPtr CreationResult
            );

            ///<summary>
            ///Creates a new .wim file or opens an existing .wim file.
            ///</summary>
            ///<param name="imageFile">Path to the .wim file to open or to create.</param>
            ///<param name="access">Specifies the file access to grant the file.</param>
            ///<param name="mode">Specifies the mode in which the file should be opened or created.</param>
            ///<returns>If the function succeeds, the return value is an open handle to the specified image file.
            ///If the function fails, the return value is NULL.</returns>
            public
            static
            IntPtr
            CreateFile(string imageFile, uint access, uint mode)
            {
                IntPtr creationResult = IntPtr.Zero;
                IntPtr windowsImageHandle = IntPtr.Zero;
                int rc = -1;

                windowsImageHandle = NativeMethods.WimCreateFile(imageFile, access, mode, 0, 0, out creationResult);
                rc = Marshal.GetLastWin32Error();
                if (windowsImageHandle == IntPtr.Zero)
                {
                    //
                    //Everything failed; throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to open/create .wim file {0}. Error = {1}",
                                                                             imageFile, rc));
                }

                return windowsImageHandle;
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMCloseHandle",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimCloseHandle(
                IntPtr Handle
            );

            ///<summary>
            ///Closes an open .wim file or an image handle.
            ///</summary>
            ///<param name="handle">Handle to an open imaging-based object.</param>
            ///<returns>If the function succeeds, the return value is nonzero.
            ///If the function fails, the return value is zero. </returns>
            public
            static
            void
            CloseHandle(IntPtr handle)
            {
                bool status = NativeMethods.WimCloseHandle(handle);
                int rc = Marshal.GetLastWin32Error();
                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                         "Unable to close image handle. Error = {0}", rc));
                }
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMSetTemporaryPath",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimSetTemporaryPath(
                IntPtr Handle,
                [MarshalAs(UnmanagedType.LPWStr)] string TemporaryPath
            );

            ///<summary>
            ///Sets the location where temporary imaging files are to be stored.
            ///</summary>
            ///<param name="handle">Handle to a WIM file returned by CreateFile</param>
            ///<param name="temporaryPath">String value of path to set as a temporary location.</param>
            ///<returns>If the function succeeds, the return value is nonzero.
            ///If the function fails, the return value is NULL.</returns>
            public
            static
            void
            SetTemporaryPath(IntPtr handle, string temporaryPath)
            {
                bool status = NativeMethods.WimSetTemporaryPath(handle, temporaryPath);
                int rc = Marshal.GetLastWin32Error();
                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to set temporary path. Error = {0}", rc));
                }
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMLoadImage",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            IntPtr
            WimLoadImage(
                IntPtr Handle,
                uint ImageIndex
            );

            ///<summary>
            ///Loads a volume image or from within a .wim file.
            ///</summary>
            ///<param name="handle">Wim handle.</param>
            ///<param name="imageIndex">Index of the image to load.</param>
            ///<returns>If the function succeeds, the return value is a handle to an object representing the volume image. 
            ///If the function fails, the return value is NULL. </returns>
            public
            static
            IntPtr
            LoadImage(IntPtr handle, int imageIndex)
            {
                //Load the image data based on the .wim handle
                //
                IntPtr hWim = NativeMethods.WimLoadImage(handle, (uint)imageIndex);
                int rc = Marshal.GetLastWin32Error();
                if (hWim == IntPtr.Zero)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to load image. Error = {0}", rc));
                }

                return hWim;

            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMCaptureImage",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            IntPtr
            WimCaptureImage(
                IntPtr Handle,
                [MarshalAs(UnmanagedType.LPWStr)] string Path,
                uint CaptureFlags
            );

            ///<summary>
            ///Captures an image from a drive root or from a directory path and stores it in an image file.
            ///</summary>
            ///<param name="handle">Handle to a .wim file returned by CreateFile.</param>
            ///<param name="path">Drive root or directory path from where the image data will be captured.</param>
            ///<returns>Handle to an object representing the volume image. If the function fails, the return value is NULL.</returns>
            public
            static
            IntPtr
            CaptureImage(IntPtr handle, string path)
            {
                IntPtr hImage = NativeMethods.WimCaptureImage(handle, path, 0);
                int rc = Marshal.GetLastWin32Error();
                if (hImage == IntPtr.Zero)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Failed to capture image from {0}. Error = {1}", path, rc));
                }
                return hImage;
            }

            ///<summary>
            ///Gets the number of volume images stored in a .wim file.
            ///</summary>
            ///<param name="Handle">Handle to a .wim file returned by CreateFile.</param>
            ///<returns>The number of images within the .wim file. </returns>
            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMGetImageCount",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            int
            WimGetImageCount(
                IntPtr Handle
            );

            ///<summary>
            ///Returns the number of volume images stored in a .wim file.
            ///</summary>
            ///<param name="windowsImageHandle">Handle to a .wim file returned by WIMCreateFile.</param>
            ///<returns>The return value is the number of images within the .wim file.
            ///If this value is zero, then the .wim file is invalid or does not contain any images that can be applied.
            ///</returns>
            public
            static
            int
            GetImageCount(IntPtr windowsImageHandle)
            {
                int count = NativeMethods.WimGetImageCount(windowsImageHandle);
                int rc = Marshal.GetLastWin32Error();
                if (count == -1)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to get image count. Error = {0}", rc));
                }

                return count;
            }

            //[DllImport("Wimgapi.dll",
            //    ExactSpelling = true,
            //    EntryPoint = "WIMMountImageHandle",
            //    CallingConvention = CallingConvention.StdCall,
            //    SetLastError = true)]
            //private static extern
            //bool
            //WIMMountImageHandle(
            //    IntPtr hImage,
            //    [MarshalAs(UnmanagedType.LPWStr)] string pszMountPath,
            //    uint dwMountFlags
            //    );
            /////<summary>
            /////Mounts an image in a .wim file to the specified directory.
            /////</summary>
            /////<returns>Returns TRUE and sets the LastError to ERROR_SUCCESS.
            /////Returns FALSE in case of a failure and the LastError is set to the appropriate Win32 error value.
            /////</returns>
            //public
            //static
            //void
            //MountImageHandle(string mountPath, )
            //{
            //    bool status = false;
            //    int rc;

            //    try
            //    {
            //        status = NativeMethods.WimMountImage(mountPath,
            //                                             windowsImageFileName,
            //                                             (uint)imageIndex,
            //                                             System.Environment.GetEnvironmentVariable("temp"));
            //        rc = Marshal.GetLastWin32Error();
            //    }
            //    catch (System.StackOverflowException)
            //    {
            //        //
            //        //Throw an exception
            //        //
            //        throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
            //                                                                 "Unable to mount image {0} to {1}.", windowsImageFileName, mountPath));
            //    }
            //    if (status == false)
            //    {
            //        //
            //        //Throw an exception
            //        //
            //        throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
            //                                                                 "Unable to mount image {0} to {1}. Error = {2}",
            //                                                                 windowsImageFileName, mountPath, rc));
            //    }
            //}


            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMMountImage",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimMountImage(
                [MarshalAs(UnmanagedType.LPWStr)] string MountPath,
                [MarshalAs(UnmanagedType.LPWStr)] string WimFileName,
                uint ImageIndex,
                [MarshalAs(UnmanagedType.LPWStr)] string TemporaryPath

            );

            ///<summary>
            ///Mounts an image in a .wim file to the specified directory.
            ///</summary>
            ///<returns>Returns TRUE and sets the LastError to ERROR_SUCCESS.
            ///Returns FALSE in case of a failure and the LastError is set to the appropriate Win32 error value.
            ///</returns>
            public
            static
            void
            MountImage(string mountPath, string windowsImageFileName, int imageIndex, string temporaryPath)
            {
                bool status = false;
                int rc;

                try
                {
                    status = NativeMethods.WimMountImage(mountPath,
                                                         windowsImageFileName,
                                                         (uint)imageIndex,
                                                         temporaryPath);
                    rc = Marshal.GetLastWin32Error();
                }
                catch (System.StackOverflowException)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to mount image {0} to {1}.", windowsImageFileName, mountPath));
                }
                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to mount image {0} to {1}. Error = {2}",
                                                                             windowsImageFileName, mountPath, rc));
                }
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMApplyImage",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimApplyImage(
                IntPtr Handle,
                [MarshalAs(UnmanagedType.LPWStr)] string Path,
                uint Flags
            );

            ///<summary>
            ///Applies an image to a drive root or to a directory path from a .wim file.
            ///</summary>
            ///<returns>If the function succeeds, the return value is nonzero.
            ///If the function fails, the return value is zero</returns>
            public
            static
            void
            ApplyImage(IntPtr imageHandle, string applicationPath)
            {
                //
                //Call WimApplyImage always with the Index flag for performance reasons.
                //
                bool status = NativeMethods.WimApplyImage(imageHandle, applicationPath, NativeMethods.WIM_FLAG_INDEX);
                int rc = Marshal.GetLastWin32Error();
                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to apply image to {0}. Error = {1}", applicationPath, rc));
                }
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMGetImageInformation",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimGetImageInformation(
                IntPtr Handle,
                out IntPtr ImageInfo,
                out IntPtr SizeOfImageInfo
            );

            ///<summary>
            ///Returns information about an image within the .wim file.
            ///</summary>
            ///<param name="handle">Handle returned by CreateImage, LoadImage</param>
            ///<returns>If the function succeeds, the return value is nonzero.
            ///If the function fails, the return value is zero.
            ///</returns>
            public
            static
            string
            GetImageInformation(IntPtr handle)
            {
                IntPtr info = IntPtr.Zero, sizeOfInfo = IntPtr.Zero;
                bool status;

                status = NativeMethods.WimGetImageInformation(handle, out info, out sizeOfInfo);
                int rc = Marshal.GetLastWin32Error();

                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to get image information. Error = {0}", rc));
                }
                string s = Marshal.PtrToStringUni(info);

                //If the function succeeds, return the pointer to the string. Otherwise, return NULL.
                //
                return s;
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMSetImageInformation",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimSetImageInformation(
                IntPtr Handle,
                IntPtr ImageInfo,
                uint SizeOfImageInfo
            );

            ///<summary>
            ///Stores information about an image within the .wim file.
            ///</summary>
            ///<param name="handle">Handle returned by CreateImage, LoadImage</param>
            ///<param name="imageInfo">String containing the unicode XML data to set.</param>
            ///<returns>If the function succeeds, the return value is nonzero.
            ///If the function fails, the return value is zero. </returns>
            public
            static
            void
            SetImageInformation(IntPtr handle, string imageInfo)
            {
                //Create a byte array for the stream, allocate some unmanaged memory, and then copy the bytes to the unmanaged memory.
                //
                byte[] byteBuffer = Encoding.Unicode.GetBytes(imageInfo);
                int byteBufferSize = byteBuffer.Length;
                IntPtr xmlBuffer = Marshal.AllocHGlobal(byteBufferSize);
                Marshal.Copy(byteBuffer, 0, xmlBuffer, byteBufferSize);

                bool status = NativeMethods.WimSetImageInformation(handle, xmlBuffer, (uint)byteBufferSize);
                int rc = Marshal.GetLastWin32Error();
                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to set image information. Error = {0}", rc));
                }
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMUnmountImage",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimUnmountImage(
                [MarshalAs(UnmanagedType.LPWStr)] string MountPath,
                [MarshalAs(UnmanagedType.LPWStr)] string WimFileName,
                uint ImageIndex,
                bool CommitChanges
            );

            ///<summary>
            ///Unmounts a mounted image in a .wim file from the specified directory.
            ///</summary>
            ///<returns>Returns TRUE and sets the LastError to ERROR_SUCCESS. Returns FALSE in case of a failure and the LastError is set
            ///to the appropriate Win32 error value.</returns>
            public
            static
            void
            DismountImage(string mountPath, string wimdowsImageFileName, int imageIndex, bool commitChanges)
            {
                bool status = false;
                int rc;

                try
                {
                    status = NativeMethods.WimUnmountImage(mountPath, wimdowsImageFileName, (uint)imageIndex, commitChanges);
                    rc = Marshal.GetLastWin32Error();
                }
                catch (System.StackOverflowException ex)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.StackOverflowException(string.Format(CultureInfo.CurrentCulture,
                                                                          "Unable to unmount image {0} from {1}.", wimdowsImageFileName, mountPath),
                                                                          ex.InnerException);
                }
                if (status == false)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                                                                             "Unable to unmount image {0} from {1}. Error = {2}",
                                                                             wimdowsImageFileName, mountPath, rc));
                }
            }

            ///<summary>
            ///User-defined function used with the RegisterMessageCallback or UnregisterMessageCallback function.
            ///</summary>
            ///<param name="MessageId">Specifies the message being sent.</param>
            ///<param name="wParam">Specifies additional message information. The contents of this parameter depend on the value of the
            ///MessageId  parameter.</param>
            ///<param name="lParam">Specifies additional message information. The contents of this parameter depend on the value of the
            ///MessageId  parameter.</param>
            ///<param name="UserData">Specifies the user-defined value passed to RegisterCallback.</param>
            ///<returns>
            ///To indicate success and to enable other subscribers to process the message return WIM_MSG_SUCCESS.
            ///To prevent other subscribers from receiving the message, return WIM_MSG_DONE.
            ///To cancel an image apply or capture, return WIM_MSG_ABORT_IMAGE when handling the WIM_MSG_PROCESS message.
            ///</returns>
            public
            delegate
            uint
            MessageCallback(
                uint MessageId,
                IntPtr wParam,
                IntPtr lParam,
                IntPtr UserData
            );

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMRegisterMessageCallback",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            uint
            WimRegisterMessageCallback(
                IntPtr hWim,
                MessageCallback MessageProc,
                IntPtr ImageInfo
            );

            ///<summary>
            ///Registers a function to be called with imaging-specific data.
            ///</summary>
            public
            static
            void
            RegisterCallback(MessageCallback callback)
            {
                uint callbackZeroBasedIndex = NativeMethods.WimRegisterMessageCallback(IntPtr.Zero, callback, IntPtr.Zero);
                int rc = Marshal.GetLastWin32Error();
                if (rc != 0)
                {
                    //
                    //Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to register message callback."));
                }
            }

            [DllImport("Wimgapi.dll",
                       ExactSpelling = true,
                       EntryPoint = "WIMUnregisterMessageCallback",
                       CallingConvention = CallingConvention.StdCall,
                       SetLastError = true)]
            private static extern
            bool
            WimUnregisterMessageCallback(
                IntPtr hWim,
                MessageCallback MessageProc
            );

            ///<summary>
            ///Unregisters a function from being called with imaging-specific data.
            ///</summary>
            ///<param name="registeredCallback">Callback to be unregistered.</param>
            public
            static
            void
            UnregisterMessageCallback(MessageCallback registeredCallback)
            {
                bool status = NativeMethods.WimUnregisterMessageCallback(IntPtr.Zero, registeredCallback);
                int rc = Marshal.GetLastWin32Error();
                if (status != true)
                {
                    //
                    // Throw an exception
                    //
                    throw new System.InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unable to unregister message callback."));
                }
            }

            private const uint WM_APP = 0x8000;

            ///<summary>
            ///Imaging Messages
            ///</summary>
            public enum WIMMessage : uint
            {
                WIM_MSG = WM_APP + 0x1476,
                WIM_MSG_TEXT,
                ///<summary>
                ///Indicates an update in the progress of an image application.
                ///</summary>
                WIM_MSG_PROGRESS,
                ///<summary>
                ///Enables the caller to prevent a file or a directory from being captured or applied.
                ///</summary>
                WIM_MSG_PROCESS,
                ///<summary>
                ///Indicates that volume information is being gathered during an image capture.
                ///</summary>
                WIM_MSG_SCANNING,
                ///<summary>
                ///Indicates the number of files that will be captured or applied.
                ///</summary>
                WIM_MSG_SETRANGE,
                ///<summary>
                ///Indicates the number of files that have been captured or applied.
                ///</summary>
                WIM_MSG_SETPOS,
                ///<summary>
                ///Indicates that a file has been either captured or applied.
                ///</summary>
                WIM_MSG_STEPIT,
                ///<summary>
                ///Enables the caller to prevent a file resource from being compressed during a capture.
                ///</summary>
                WIM_MSG_COMPRESS,
                ///<summary>
                ///Alerts the caller that an error has occurred while capturing or applying an image.
                ///</summary>
                WIM_MSG_ERROR,
                ///<summary>
                ///Enables the caller to align a file resource on a particular alignment boundary.
                ///</summary>
                WIM_MSG_ALIGNMENT,
                WIM_MSG_RETRY,
                ///<summary>
                ///Enables the caller to align a file resource on a particular alignment boundary.
                ///</summary>
                WIM_MSG_SPLIT,
                WIM_MSG_FILEINFO,
                WIM_MSG_INFO,
                WIM_MSG_WARNING,
                WIM_MSG_CHK_PROCESS,
                WIM_MSG_WARNING_OBJECTID,
                WIM_MSG_STALE_MOUNT_DIR,
                WIM_MSG_STALE_MOUNT_FILE,
                WIM_MSG_MOUNT_CLEANUP_PROGRESS,
                WIM_MSG_SUCCESS = 0x00000000,
                WIM_MSG_ABORT_IMAGE = 0xFFFFFFFF
            };

            ///<summary>
            ///Capture will do byte-by-byte verification of single instance files.
            ///</summary>
            public const uint WIM_FLAG_VERIFY = 0x00000002;
            ///<summary>
            ///Specifies that the image is to be sequentially read for caching/performance purposes.
            ///</summary>
            public const uint WIM_FLAG_INDEX = 0x00000004;

        }

        ///<summary>
        ///Maps CreateFileAccess to CreateFileAccessPrivate
        ///</summary>
        ///
        private
        CreateFileAccessPrivate
        GetMappedFileAccess(CreateFileAccess access)
        {
            //
            //Map the file access specified from an int to uint.
            //
            CreateFileAccessPrivate fileAccess;
            switch (access)
            {
                case CreateFileAccess.Read:
                    fileAccess = CreateFileAccessPrivate.Read;
                    break;
                case CreateFileAccess.Write:
                    fileAccess = CreateFileAccessPrivate.Write;
                    break;
                default:
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "No file access level specified."));
            }
            return fileAccess;
        }

        //[CLSCompliant(false)]
        [FlagsAttribute]
        private
        enum
        CreateFileAccessPrivate : uint
        {
            ///<summary>
            ///Mapping from CreateFileAccess.Read
            ///</summary>
            Read = 0x80000000,
            ///<summary>
            ///Mapping from CreateFileAccess.Write
            ///</summary>
            Write = 0x40000000
        }

        ///<summary>
        ///Image event messages.
        ///</summary>
        //[CLSCompliant(false)]
        public
        enum
        ImageEventMessage : uint
        {
            ///<summary>
            ///Enables the caller to prevent a file or a directory from being captured or applied.
            ///</summary>
            Progress = NativeMethods.WIMMessage.WIM_MSG_PROGRESS,
            ///<summary>
            ///Notification sent to enable the caller to prevent a file or a directory from being captured or applied.
            ///To prevent a file or a directory from being captured or applied, call WindowsImageContainer.SkipFile().
            ///</summary>
            Process = NativeMethods.WIMMessage.WIM_MSG_PROCESS,
            ///<summary>
            ///Enables the caller to prevent a file resource from being compressed during a capture.
            ///</summary>
            Compress = NativeMethods.WIMMessage.WIM_MSG_COMPRESS,
            ///<summary>
            ///Alerts the caller that an error has occurred while capturing or applying an image.
            ///</summary>
            Error = NativeMethods.WIMMessage.WIM_MSG_ERROR,
            ///<summary>
            ///Enables the caller to align a file resource on a particular alignment boundary.
            ///</summary>
            Alignment = NativeMethods.WIMMessage.WIM_MSG_ALIGNMENT,
            ///<summary>
            ///Enables the caller to align a file resource on a particular alignment boundary.
            ///</summary>
            Split = NativeMethods.WIMMessage.WIM_MSG_SPLIT,
            ///<summary>
            ///Indicates that volume information is being gathered during an image capture.
            ///</summary>
            Scanning = NativeMethods.WIMMessage.WIM_MSG_SCANNING,
            ///<summary>
            ///Indicates the number of files that will be captured or applied.
            ///</summary>
            SetRange = NativeMethods.WIMMessage.WIM_MSG_SETRANGE,
            ///<summary>
            ///Indicates the number of files that have been captured or applied.
            /// </summary>
            SetPos = NativeMethods.WIMMessage.WIM_MSG_SETPOS,
            ///<summary>
            ///Indicates that a file has been either captured or applied.
            ///</summary>
            StepIt = NativeMethods.WIMMessage.WIM_MSG_STEPIT,
            ///<summary>
            ///Success.
            ///</summary>
            Success = NativeMethods.WIMMessage.WIM_MSG_SUCCESS,
            ///<summary>
            ///Abort.
            ///</summary>
            Abort = NativeMethods.WIMMessage.WIM_MSG_ABORT_IMAGE,
            
            MountCleanupProgress = NativeMethods.WIMMessage.WIM_MSG_MOUNT_CLEANUP_PROGRESS
        }

        //
        //WindowsImageContainer Member Data
        //
        private IntPtr m_ImageContainerHandle;  //Handle to the .wim file
        private string m_WindowsImageFilePath;  //Path to the .wim file

        private WindowsImage[] m_Images;        //Array of image objects inside a .wim file
        private int m_ImageCount;               //Number of images inside a .wim file

        //
        //DO NOT CHANGE!
        //
        private static NativeMethods.MessageCallback m_MessageCallback;
    }

    ///<summary>
    ///Describes the file that is being processed for the ProcessFileEvent.
    ///</summary>
    public
    class
    DefaultImageEventArgs : EventArgs
    {
        ///<summary>
        ///Default constructor.
        ///</summary>
        public
        DefaultImageEventArgs(IntPtr wideParameter, IntPtr leftParameter, IntPtr userData)
        {
            m_wParam = wideParameter;
            m_lParam = leftParameter;
            m_UserData = userData;


        }
        ///<summary>
        ///wParam
        ///</summary>
        public IntPtr WideParameter
        {
            get
            {
                return m_wParam;
            }
        }
        ///<summary>
        ///lParam
        ///</summary>
        public IntPtr LeftParameter
        {
            get
            {
                return m_lParam;
            }
        }
        ///<summary>
        ///UserData
        ///</summary>
        public IntPtr UserData
        {
            get
            {
                return m_UserData;
            }
        }

        private IntPtr m_wParam;
        private IntPtr m_lParam;
        private IntPtr m_UserData;
    }

    ///<summary>
    ///Describes the file that is being processed for the ProcessFileEvent.
    ///</summary>
    public
    class
    ProcessFileEventArgs : EventArgs
    {
        ///<summary>
        ///Default constructor.
        ///</summary>
        ///<param name="file">Fully qualified path and file name. For example: c:\file.sys.</param>
        ///<param name="skipFileFlag">Default is false - skip file and continue.
        ///Set to true to abort the entire image capture.</param>
        public
        ProcessFileEventArgs(string file, IntPtr skipFileFlag)
        {
            m_FilePath = file;
            m_SkipFileFlag = skipFileFlag;
        }

        ///<summary>
        ///Skip file from being imaged.
        ///</summary>
        public
        void
        SkipFile()
        {
            byte[] byteBuffer = {
                    0
                };
            int byteBufferSize = byteBuffer.Length;
            Marshal.Copy(byteBuffer, 0, m_SkipFileFlag, byteBufferSize);
        }

        ///<summary>
        ///Fully qualified path and file name.
        ///</summary>
        public string FilePath
        {
            get
            {
                string stringToReturn = "";
                if (m_FilePath != null)
                {
                    stringToReturn = m_FilePath;
                }
                return stringToReturn;
            }
        }

        ///<summary>
        ///Flag to indicate if the entire image capture should be aborted.
        ///Default is false - skip file and continue. Setting to true will
        ///abort the entire image capture.
        ///</summary>
        public bool Abort
        {
            set
            {
                m_Abort = value;
            }

            get
            {
                return m_Abort;
            }
        }
        private string m_FilePath;
        private bool m_Abort;
        private IntPtr m_SkipFileFlag;

    }
}
