jeffkl / ManagedWimgApi

A managed wrapper for the native Windows Imaging API (WIMGAPI).
MIT License
36 stars 12 forks source link

Capture image take too much time #16

Open JerryYCChang opened 6 years ago

JerryYCChang commented 6 years ago

Hi,

I use this WimgApi build a UI to capture image, I had try different compression type and all of it will take a long time. If with none compression type, it will take about 60-70 minutes, with Lzx compression type will need about 80 minutes. If I use Dism command to capture image with maximum compression, it need just about 25 minutes and with fast compression, it need about 20 minutes. The source file and the hardware environment are the same. Is it normal that use WimgApi will need more time than use Dism command to capture image? Thank you~

Herohtar commented 6 years ago

I thought I was experiencing the same issue, but after doing some direct comparison tests there doesn't seem to be much difference in capture time for me, though Dism did appear to be slightly faster.

I made a backup using the WimgApi with Lzx compression and it seemed like it didn't report any progress for a long time -- it took a few minutes before it even moved to 1%, and then stayed at 1% for a long time after that, but in the end it completed the capture in ~78 minutes.

I then tried the same backup just using Dism from the command line with compression set to max. The Dism capture completed in ~67 minutes.

josemesona commented 6 years ago

@JerryYCChang do you have some sample code of how you're calling the managed API? In the end, it simply calls the native API so it shouldn't be any slower or faster. But you could be registering a callback that is slowing things down or something else could be awry.

@Herohtar do you want to post a repro of code that reports progress as you experienced? I'd like to dig into what's wrong.

JerryYCChang commented 6 years ago

@josemesona Here is part of my code, the compression type will be change in different type. string TempPath = Application.StartupPath;

        try
        {
            //MessageBox.Show("Start CreateFile !");
            Microsoft.Wim.WimHandle wimHandle = Microsoft.Wim.WimgApi.CreateFile(imageFilePath, Microsoft.Wim.WimFileAccess.Write, Microsoft.Wim.WimCreationDisposition.CreateAlways, Microsoft.Wim.WimCreateFileOptions.None, Microsoft.Wim.WimCompressionType.None);

            if(wimHandle != null)
            {
                Microsoft.Wim.WimgApi.SetTemporaryPath(wimHandle, TempPath);

                try
                {
                    //MessageBox.Show("Start Capture Image !");

                    using (Microsoft.Wim.WimHandle imageHandle = Microsoft.Wim.WimgApi.CaptureImage(wimHandle, volumeDir, Microsoft.Wim.WimCaptureImageOptions.None))
                    {
                        //MessageBox.Show("Start GetImageCount !");

                        int imageCount = Microsoft.Wim.WimgApi.GetImageCount(wimHandle);

                        //MessageBox.Show("Capture Image Finished !");
                    }

                }
                catch (Exception ex)
                {
                    MessageBox.Show("CaptureImage error : " + ex.Message);
                }

            }
            else
            {
                MessageBox.Show("wimHandle = null ");
            }

        }
        catch (Exception ex)
        {
            MessageBox.Show("CreateFile error : " + ex.Message);
        }
jeffkl commented 6 years ago

Ok I’m going to look into it. My guess is that dism is filtering out some stuff by default. You’re capturing a whole drive right? Have you compared the file size of the two WIM files?

JerryYCChang commented 6 years ago

@jeffkl I capture the OS volume on the drive. The original volume size is 12.5G, use WimgApi with compress type of none will take about 15 minutes and the WIM file size is 10.4G, with compress type of Lzx will take about 50 minutes and the WIM file size is 4.72G. Use dism command with default compress type will take about 7~8 minutes and the WIM file size is 5.05G. It may cause by the version of SDK, I use windows 10 version 1803 with the latest version of SDK.

jeffkl commented 6 years ago

I did some experiments and this is what I found:

Compressing a folder with 70,165 files, 16,718 folders, and 3.74 GB total

Dism.exeMicrosoft.Wim
CompressionTimeSizeTimeSize
Default1:001.93 GB
None0:423.30 GB0:403.30 GB
Max1:251.88 GB1:341.88 GB
Fast1:001.93 GB1:001.93 GB

Dism.exe has a /compress command-line argument that appears to default to fast which translates to WimCompressionType.Xpress. The best compression /compress:max maps to WimCompressionType.Lzx which is slower for sure.

Can you try WimCompressionType.Xpress in your code and see if it performs as fast as Dism.exe?

JerryYCChang commented 6 years ago

@jeffkl I did some experiments, too. The original OS volume have 12.5GB and the .wim file like the follow table. Sorry, I don't know how to set the table correctly, it's the same as your table.

  Dism.exe Microsoft.Wim
Compression Time Size Time Size
Default 7:35 5.05 GB    
None 14:17 9.8 GB 14:30 10.4 GB
Max 12:15 4.7 GB 49 4.72 GB
Fast 7:35 5.05 GB 28:45 5.15 GB
ied206 commented 6 years ago

There are possibility that antivirus interfered with ManagedWimgApi, dropping the performance. While I was testing ManagedWimgApi and ManagedWimLib (My C# wrapper of wimlib), I experienced similar issue. It was solved after I turn off my antivirus (V3 Lite). Many antivirus solutions are notorious for suspecting and interfering non-signed binary, which I guess this is the reason.

@JerryYCChang What is your test environment? (OS, antivirus, etc.)

JerryYCChang commented 6 years ago

@jeffkl I forgot to tell you that I was use the capture back under WinPE, is there some thing may have different to cause the capture time to be different? @ied206 The OS is WinPE, build from Win10 version 1803. There are no antivirus.

jeffkl commented 6 years ago

There could be a major performance difference with .NET Framework on WinPE vs Windows. I know that assemblies are NGen'd which improves performance. But I am not aware of any known issues with .NET Framework apps running slow in WinPE. Even if there was, I wouldn't expect it to be 4 times slower. Especially in this case since Microsoft.Wim.dll is mostly just calling the native API.

I'm stumped and unfortunately have very little time at the moment to investigate. I'm definitely interested in what's causing it, so please keep trying to get to the bottom of it. In a few weeks I should have time to set up a WinPE test environment to see if I can figure it out.

Herohtar commented 6 years ago

In my case, running a full OS drive capture with Microsoft.Wim was taking significantly longer than the same capture using DISM, and also resulting in a much larger file. After a lot of tests I finally realized that the problem was that I was assuming that Microsoft.Wim had the default exclusion list that DISM does. It turns out that the API does not exclude any files by default, while DISM excludes this following list of files/folders by default:

Specifically, the page file and hibernation file were both rather large and caused a major increase in capture time and image size. After modifying my project to exclude those files, there was no noticeable difference in Microsoft.Wim vs DISM captures.

As an interesting side note, the API does automatically skip compression on certain files (at least .zip, .cab, .exe, .dll, and .mp3, not sure if there are more).

javmarquez13 commented 3 years ago

I have a issue when try to capture the image,

When try to capture with the wimcompress.none option the code working fine, and capture the image but when y try to capture with other option for example with wimcompress.Xpress create the .wim but never finish the capture...

this is mi code that i'm using

void WIMGAPI_CaptureImage()
    {
        try
        {
            // Open a handle to the .wim file
            WimHandle _handle = WimgApi.CreateFile(@"W:\Test_LZX_Compress.wim",
                                                    WimFileAccess.Write,
                                                    WimCreationDisposition.CreateNew,
                                                    WimCreateFileOptions.Verify,
                                                    WimCompressionType.Xpress);
            try
            {
                //Always create a temporal path to create a temporal files during the capture
                WimgApi.SetTemporaryPath(_handle, @"W:\");

                //Create delegate to get information during de capturing image
                //wimMessageCallback = new WimMessageCallback(WimCallBack); verify if is necessary create a instance

                //Register the callback method with the specific handle returned by wimgapi.CreateFile
                WimgApi.RegisterMessageCallback(_handle, WimCallBackMethod);

                //Call function to Capture the image
                _handleByCapturingImg = WimgApi.CaptureImage(_handle, @"E:\", WimCaptureImageOptions.None);
            }
            finally
            {
                // Be sure to unregister the callback method                    
                WimgApi.UnregisterMessageCallback(_handle, WimCallBackMethod);
            }

            //Be sure that close the handle for the capturing image next close the handle for the createfile
            _handleByCapturingImg.Close();
            _handle.Close();

            MessageBox.Show("Capture Image Finished");
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

 private WimMessageResult WimCallBackMethod(WimMessageType _MessageType, object _message, object _userData)
    {
        // This method is called for every single action during the process being executed.
        // In the case of apply, you'll get Progress, Info, Warnings, Errors, etc
        //
        // The trick is to determine the message type and cast the "message" param to the corresponding type
        //
        _WimMessageResult = WimMessageResult.Success;

        switch (_MessageType)
        {
            case WimMessageType.Progress:  // Some progress is being sent

                // Get the message as a WimMessageProgress object
                //
                WimMessageProgress progressMessage = (WimMessageProgress)_message;

                // Print the progress
                //
                lblProgress.Text =  "progress: " +  progressMessage.PercentComplete.ToString("#0.##%");
                lblEstimatedTime.Text = "Estimated Time Remaining: " + progressMessage.EstimatedTimeRemaining.TotalMinutes.ToString();                    
                lblEstimatedTime.Refresh();
                lblProgress.Refresh();
                break;

            case WimMessageType.Process:  // Some progress is being sent

                // Get the message as a WimMessageProgress object
                //
                WimMessageProcess processMessage = (WimMessageProcess)_message;

                // Print the Current file 
                //
                lblFilesInfo.Text = "File: " + processMessage.Path;                   
                lblFilesInfo.Refresh();
                break;

            case WimMessageType.Scanning:  // Some progress is being sent

                // Get the message as a WimMessageProgress object
                //
                WimMessageScanning scanningMessage = (WimMessageScanning)_message;

                // Print the Current file 
                //
                lblCountFiles.Text = "Count Files: " + scanningMessage.Count.ToString();
                lblCountFiles.Refresh();
                break;

            case WimMessageType.Compress:  // Some progress is being sent

                // Get the message as a WimMessageProgress object
                //
                WimMessageCompress compressMessage = (WimMessageCompress)_message;

                // Print the Current file 
                //
                lblFilesInfo.Text = "Compress File: " + compressMessage.Path;
                lblFilesInfo.Refresh();
                break;

            case WimMessageType.Warning:  // A warning is being sent

                // Get the message as a WimMessageProgress object
                //
                WimMessageWarning warningMessage = (WimMessageWarning)_message;

                // Print the file and error code
                //
                _WimMessageResult = WimMessageResult.Abort;
                break;

            case WimMessageType.Error:  // An error is being sent

                // Get the message as a WimMessageError object
                //
                WimMessageError errorMessage = (WimMessageError)_message;

                // Print the file and error code
                //
                Console.WriteLine("Error: {0} ({1})", errorMessage.Path, errorMessage.Win32ErrorCode);
                _WimMessageResult = WimMessageResult.Abort;
                break;
        }

        // Depending on what this method returns, the WIMGAPI will continue or cancel.
        //
        // Return WimMessageResult.Abort to cancel.  In this case we return Success so WIMGAPI keeps going

        return _WimMessageResult;      
    }

image

jonmiller1 commented 5 months ago

@javmarquez13 I see you manipulating form controls inside of your callback event. That is going to cause some serious performance issues and may be causing your other issues too. Updating UI controls is slow, especially when that callback is getting called hundreds of thousands of times. Consider running the Microsoft.Wim calls on a different thread/background worker, pass updated info through the ProgressChanged callback in the background worker, and finally throttle the raising of that event, don't raise it more than once a second. You should see a noticeable performance gain.