Search code examples
c#windows-installerprogress-bar

Creating an accurate progress bar for remote installer application in C#


Very new to C# here, and I'm learning a lot as I go along.

I'm creating a Winforms app that installs patches remotely. Basically, you give it a file (.msi or .exe) and a computer list, and it goes down the list until all of the patches have been installed. It seems to be working for what I want it to do. Just a click and go thing for vuln/patch management. For the record, I use PSexec and powershell to do the same task, and they're wonderful. Just fiddling around with my own, hoping to do some learning in the process.

I want to create an accurate progress bar for the application, so the admin can have a general idea of what processes are being handled at the time. There is a very wide spread in the amount of systems that may need to be patched, anywhere from 10 systems to 1K+.

I have looked at tutorials of progress bar implementation, but most are based on the code writer estimating the time of task completion for specific jobs. Being that every patch size, install time, and amount of computers are different, that doesn't seem to help me much.

private void Start_Click(object sender, EventArgs e)
    {
        string complist = openFileDialog2.FileName;
        string patch = textBox2.Text;
        string fileName = patch;
        string patchfile = Path.GetFileName(fileName);

        foreach (string line in File.ReadLines(complist))
        {
            //Checks if C:\PatchUpdates exists on remote machine. If not, a folder is created.
            if (!Directory.Exists(@"\\" + line + @"\C$\PatchUpdates"))
            {
                Directory.CreateDirectory(@"\\" + line + @"\C$\PatchUpdates");
            }

            //XCOPY patch to every computer
            System.Diagnostics.Process processCopy = new System.Diagnostics.Process();
            ProcessStartInfo StartInfo = new ProcessStartInfo();
            StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            StartInfo.FileName = "cmd";
            StartInfo.Arguments = string.Format("/c xcopy " + patch + " " + @"\\{0}\C$\PatchUpdates /K /Y", line);
            processCopy.StartInfo = StartInfo;
            processCopy.Start();
            processCopy.WaitForExit();


            //Checks filename and installs according to file type.
            if (patch.Contains(".msi"))
            {
                //Uses WMI to execute a remote command to install using msiexec.
                ConnectionOptions options = new ConnectionOptions();
                options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
                ManagementScope WMIscope = new ManagementScope(
                string.Format("\\\\{0}\\root\\cimv2", line));
                WMIscope.Connect();
                ManagementClass WMIprocess = new ManagementClass(
                    WMIscope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
                object[] processmsi = { @"cmd.exe /c msiexec /qn /i " + @"C:\PatchUpdates\" + patchfile + @" /norestart" };
                object result = WMIprocess.InvokeMethod("Create", processmsi);
            }

            else if (patch.Contains(".exe"))
            {
                //Uses WMI to execute a remote command to install using commandline.
                ConnectionOptions options = new ConnectionOptions();
                options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
                ManagementScope WMIscope = new ManagementScope(
                    string.Format("\\\\{0}\\root\\cimv2", line));
                WMIscope.Connect();
                ManagementClass WMIprocess = new ManagementClass(
                    WMIscope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
                object[] processexe = { @"cmd.exe /c C:\PatchUpdates\" + patchfile + @" /silent /norestart" };
                object result = WMIprocess.InvokeMethod("Create", processexe);
            }

            else if (patch.Contains(".msu"))
            {
                //Uses WMI to execute a remote command to install using WUSA.
                ConnectionOptions options = new ConnectionOptions();
                options.Impersonation = System.Management.ImpersonationLevel.Impersonate;
                ManagementScope WMIscope = new ManagementScope(
                    string.Format("\\\\{0}\\root\\cimv2", line));
                WMIscope.Connect();
                ManagementClass WMIprocess = new ManagementClass(
                    WMIscope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
                object[] processmsu = { @"wusa " + patchfile + " /quiet /norestart" };
                object result = WMIprocess.InvokeMethod("Create", processmsu);
            }
        }
    }

The code above is where most of the work is done. When the user clicks "Start", the patch is copied to C:\PatchUpdates on every machine and is installed using WMI.

How could I make a progress bar that is based on the calculation of time taken to do each task, and finishes at 100% when the last computers are being patched? I'm assuming a lot of work is needed to do this.

Any help is appreciated.


Solution

  • You need to first get the amount of lines (you systems count).

    If you are using .Net 4.0 or later you can use

    var lineCount = File.ReadLines(@"C:\file.txt").Count();
    

    else you can do first

    var lineCount = 0;
    using (var reader = File.OpenText(@"C:\file.txt"))
    {
        while (reader.ReadLine() != null)
        {
            lineCount++;
        }
    }
    

    After that you set the progressbars minimum to 0 and the Maximum to this count of systems. In the foreach you need only to do a progressbar.PerformStep.

    public void loadFiles()
    {
       // Sets the progress bar's minimum value to a number representing
       // no operations complete -- in this case, no files read.
       progressBar1.Minimum = 0;
       // Sets the progress bar's maximum value to a number representing
       // all operations complete -- in this case, all five files read.
       progressBar1.Maximum = Convert.ToInt(lineCount); // in our case to the number of systems
       // Sets the Step property to amount to increase with each iteration.
       // In this case, it will increase by one with every file read.
       progressBar1.Step = 1;
    
       // Uses a for loop to iterate through the operations to be
       // completed. In this case, five files are to be copied into memory,
       // so the loop will execute 5 times.
       for (int i = 0; i <= 4; i++)
       {
          // Inserts code to copy a file
          progressBar1.PerformStep();
          // Updates the label to show that a file was read.
          label1.Text = "# of Files Read = " + progressBar1.Value.ToString();
       }
    }