Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Updating main QT application using updater application
QtWS25 Last Chance

Updating main QT application using updater application

Scheduled Pinned Locked Moved Unsolved General and Desktop
14 Posts 4 Posters 771 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • L Offline
    L Offline
    lukutis222
    wrote on 3 Jul 2024, 06:33 last edited by lukutis222 7 Mar 2024, 06:34
    #1

    Hello. I want to be able to automatically update my main QT application. For this, I have created the following process:

    1. Main QT application starts

    2. Check version.txt document on the server. This document will tell whether the application uploaded to the server matches the local appliation version

    3. If the server application is higher, then download update.zip folder from the server. update.zip contains TestTool.exe, all required .dlls and other required source files :
      1b8b10fc-56d0-4fb1-97df-9572574209f8-image.png

    4. Application is then going to start applyUpdate method:

    void FileDownloader::applyUpdate() {
        // Set the path to the zip file and the destination directory
    
        QString zipFilePath = QCoreApplication::applicationDirPath() + "/update.zip";
        qDebug("Expected zip location = %s \n",zipFilePath.toStdString().c_str());
        QString destinationDir = QCoreApplication::applicationDirPath() + "/update";
    
        // Create the destination directory if it does not exist
        QDir dir(destinationDir);
        if (!dir.exists()) {
            dir.mkpath(destinationDir);
        }
    
        // Unzip the update.zip file
        QProcess process;
        process.setProgram("unzip");
        process.setArguments(QStringList() << zipFilePath << "-d" << destinationDir);
    
        // Start the process and wait for it to finish
        process.start();
        if (!process.waitForFinished()) {
            qDebug() << "Unzip failed to start: " << process.errorString();
            return;
        }
    
        // Check the exit status
        if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
            qDebug() << "Unzip failed with exit code: " << process.exitCode();
            qDebug() << "Error output: " << process.readAllStandardError();
            return;
        }
    
        qDebug() << "Unzip completed successfully.";
    
        // Proceed with the update (e.g., replace old executable, etc.)
        QString oldExecutable = QCoreApplication::applicationDirPath() + "/TestTool.exe";
        QString newExecutable = destinationDir + "/TestTool.exe";
        QString updaterExecutable = QCoreApplication::applicationDirPath() + "/updater.exe";
    
        qDebug("old executsble path = %s \n",oldExecutable.toStdString().c_str());
        qDebug("new executsble path = %s \n",newExecutable.toStdString().c_str());
        qDebug("updater executable path = %s\n", updaterExecutable.toStdString().c_str());
    
        // Start the updater process
        if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable)) {
            qDebug() << "Failed to start updater.";
            return;
        }
    
        QCoreApplication::quit();
    
    }
    

    As you can see from above, applyUpdate method unzips the update.zip using unzip.exe and then starts the updater.exe in detached mode and quits the application. updater is a custom application I have created to perform the update process of my application. The reason why I must quit main application here is because updater.exe is going to replace the old executable with the new executable. In order to do that, main application must be closed.

    My updater.exe takes in 2 arguments (old_executable_path and new_executable_path). See the code for the update:

    MainWindow::MainWindow(const QString& oldExecutable, const QString& newExecutable, QWidget* parent)
        : QMainWindow(parent), oldExecutable(oldExecutable), newExecutable(newExecutable)
    {
        // Set up the UI
        QLabel* label = new QLabel(this);
        QVBoxLayout* layout = new QVBoxLayout();
        layout->addWidget(label);
    
        QWidget* centralWidget = new QWidget(this);
        centralWidget->setLayout(layout);
        setCentralWidget(centralWidget);
    
        if (oldExecutable.isEmpty() || newExecutable.isEmpty()) {
            label->setText("Incorrect number of arguments passed. Usage: updater <oldExecutable> <newExecutable>");
        } else {
            label->setText("Updater is running...");
            // Schedule the update process
            QTimer::singleShot(2000, this, &MainWindow::performUpdate);
        }
    
    }
    
    void MainWindow::performUpdate()
    {
        qDebug() << "Starting update process";
    
    
        // Remove old executable
        if (QFile::exists(oldExecutable)) {
            qDebug() << "Removing old executable";
            QFile::remove(oldExecutable);
        }
    
        // Copy the new executable to the application directory with a temporary name
        if (QFile::exists(newExecutable)) {
            qDebug() << "Copying new executable to temporary name";
            QFile::copy(newExecutable, oldExecutable);
        }
    
        // Restart the application
        qDebug() << "Restarting application";
        QProcess::startDetached(oldExecutable);
    
        QCoreApplication::quit();
    }
    

    As you can see from above, it simply is deleting the old executable and copying new executable. After that we immediately start the new executable so the end user does not even have to restart the application since its automatically started.

    I can test this method by calling update.exe using command line (assuming I have previously downloaded and unzipped update file from the server):

    C:\Users\petrikas.lu>C:\Users\petrikas.lu\Desktop\WORK\QT\TestTool\updater.exe "C:\Users\petrikas.lu\Desktop\WORK\QT\TestTool\TestTool.exe" "C:\Users\petrikas.lu\Desktop\WORK\QT\TestTool\update\TestTool.exe"
    

    and this will work without any issues.

    However, when I try to run this via my main application, it does not work. I believe the updater does not even have a chance to start since we close main application right away:
    I believe the issue lies somewhere here:

        // Start the updater process
        if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable)) {
            qDebug() << "Failed to start updater.";
            return;
        }
        QCoreApplication::quit();
    
    

    but I cannot fully figure out what could be wrong...
    I would appreciate if someone could provide some insights...

    1 Reply Last reply
    0
    • H Offline
      H Offline
      hskoglund
      wrote on 3 Jul 2024, 06:45 last edited by
      #2

      Hi, if you're only targeting Windows (and not MacOS or Linux) then you could try an alternative approach, using the fact that on Windows, while you cannot delete the app while it's running, you can rename it, like this:

      1. delete any old detritus i.e. "TestToolOldVersion.exe"
      2. the app renames itself from "TestTool.exe" to "TestToolOldVersion.exe"
      3. the app continues with downloading, unzipping and installing the new .exe with the name "TestTool.exe"

      Optional steps:
      4) when the app exits, try starting a .bat file that deletes "TestToolOldVersion.exe"
      5) when the app starts, check the filename, if it is "TestToolOldVersion.exe" that is started by mistake, issue an error or try to start the real "TestTool.exe"

      L 1 Reply Last reply 3 Jul 2024, 07:26
      1
      • H hskoglund
        3 Jul 2024, 06:45

        Hi, if you're only targeting Windows (and not MacOS or Linux) then you could try an alternative approach, using the fact that on Windows, while you cannot delete the app while it's running, you can rename it, like this:

        1. delete any old detritus i.e. "TestToolOldVersion.exe"
        2. the app renames itself from "TestTool.exe" to "TestToolOldVersion.exe"
        3. the app continues with downloading, unzipping and installing the new .exe with the name "TestTool.exe"

        Optional steps:
        4) when the app exits, try starting a .bat file that deletes "TestToolOldVersion.exe"
        5) when the app starts, check the filename, if it is "TestToolOldVersion.exe" that is started by mistake, issue an error or try to start the real "TestTool.exe"

        L Offline
        L Offline
        lukutis222
        wrote on 3 Jul 2024, 07:26 last edited by lukutis222 7 Mar 2024, 07:27
        #3

        @hskoglund

        Thanks for suggestions. I could not fully understand what you mean, but since you mentioned I can rename the name of the exe while it is still running, I have made the following modifications:

        In my main application applyUpdate method, after starting updater.exe I no longer quit application:

        void FileDownloader::applyUpdate() {
            // Set the path to the zip file and the destination directory
        
            QString zipFilePath = QCoreApplication::applicationDirPath() + "/update.zip";
            qDebug("Expected zip location = %s \n",zipFilePath.toStdString().c_str());
            QString destinationDir = QCoreApplication::applicationDirPath() + "/update";
        
            // Create the destination directory if it does not exist
            QDir dir(destinationDir);
            if (!dir.exists()) {
                dir.mkpath(destinationDir);
            }
        
            // Unzip the update.zip file
            QProcess process;
            process.setProgram("unzip");
            process.setArguments(QStringList() << zipFilePath << "-d" << destinationDir);
        
            // Start the process and wait for it to finish
            process.start();
            if (!process.waitForFinished()) {
                qDebug() << "Unzip failed to start: " << process.errorString();
                return;
            }
        
            // Check the exit status
            if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
                qDebug() << "Unzip failed with exit code: " << process.exitCode();
                qDebug() << "Error output: " << process.readAllStandardError();
                return;
            }
        
            qDebug() << "Unzip completed successfully.";
        
            // Proceed with the update (e.g., replace old executable, etc.)
            QString oldExecutable = QCoreApplication::applicationDirPath() + "/TestTool.exe";
            QString newExecutable = destinationDir + "/TestTool.exe";
            QString updaterExecutable = QCoreApplication::applicationDirPath() + "/updater.exe";
        
            qDebug("old executsble path = %s \n",oldExecutable.toStdString().c_str());
            qDebug("new executsble path = %s \n",newExecutable.toStdString().c_str());
            qDebug("updater executable path = %s\n", updaterExecutable.toStdString().c_str());
        
            // Start the updater process
            if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable << QString::number(QCoreApplication::applicationPid()))) {
                qDebug() << "Failed to start updater.";
                return;
            }
        
            //QCoreApplication::quit();
        
        }
        

        I have made the following modifications to updater.exe:
        In the performUpdate method, I no longer try to delete an old .exe file. I just simply rename it to TestTool_renamed.exe and I put new TestTool.exe in its place. After that, I am starting detachedProcess with newExecutable. See full method:

        void MainWindow::performUpdate()
        {
            qDebug() << "Starting update process";
        
        
            // Rename old executable
            if (QFile::exists(oldExecutable)) {
                qDebug() << "Rename old executable";
                QFile::rename(oldExecutable,"C:/Users/petrikas.lu/Desktop/WORK/QT/TestTool/TestTool_renamed.exe");
            }
        
            // Copy the new executable to the application directory
            if (QFile::exists(newExecutable)) {
                qDebug() << "Copying new executable";
                if (!QFile::copy(newExecutable, oldExecutable)) {
                    qDebug() << "Failed to copy new executable:";
                    return;
                }
            }
        
                // Kill the old application
                QProcess process;
                process.start("kill", QStringList() << "-0" << QString::number(mainAppPid));
                process.waitForFinished();
        
            // Restart the application
            qDebug() << "Restarting application";
            QProcess::startDetached(newExecutable);
        
            
              QCoreApplication::quit();
        }
        

        This method works. The only issue now is the old application remains running even after the new is started. So now I have 2 TestTool.exe running (one old version and the other updated).

        To solve this issue, I have tried to also add a 3rd argument to my updater.exe (applicationPid). So now my updater.exe takes in 3 arguments:

        1. Path to old executable
        2. Path to new executable
        3. Main applicationPid

        I have tried to kill the old application from the updater.exe using the Main applicationPid that is passed as an argument

                // Kill the old application
                QProcess process;
                process.start("kill", QStringList() << "-0" << QString::number(mainAppPid));
                process.waitForFinished();
        

        but it does not work.

        1 Reply Last reply
        0
        • A Offline
          A Offline
          artwaw
          wrote on 3 Jul 2024, 07:37 last edited by
          #4

          My approach to creating autoupdater was slightly different:

          • download the update;
          • start the update QProcess as detached https://doc.qt.io/qt-6/qprocess.html#startDetached-1
          • if QProcess started succeffuly, self terminate.
            And update QProcess, should everything complete ok, would start the program anew. Can digout the code if one wishes.

          My main reasoning was - since the widnows exe can't fully manipulate itself, let it just download the files and handover update to the specified subroutine, then self terminate to let said subroutine work.

          For more information please re-read.

          Kind Regards,
          Artur

          L 1 Reply Last reply 3 Jul 2024, 07:41
          2
          • A artwaw
            3 Jul 2024, 07:37

            My approach to creating autoupdater was slightly different:

            • download the update;
            • start the update QProcess as detached https://doc.qt.io/qt-6/qprocess.html#startDetached-1
            • if QProcess started succeffuly, self terminate.
              And update QProcess, should everything complete ok, would start the program anew. Can digout the code if one wishes.

            My main reasoning was - since the widnows exe can't fully manipulate itself, let it just download the files and handover update to the specified subroutine, then self terminate to let said subroutine work.

            L Offline
            L Offline
            lukutis222
            wrote on 3 Jul 2024, 07:41 last edited by
            #5

            @artwaw This sound like exactly my approach. Why do you think mine is different?

            A J 2 Replies Last reply 3 Jul 2024, 07:52
            0
            • L lukutis222
              3 Jul 2024, 07:41

              @artwaw This sound like exactly my approach. Why do you think mine is different?

              A Offline
              A Offline
              artwaw
              wrote on 3 Jul 2024, 07:52 last edited by
              #6

              @lukutis222

              QProcess process;
              process.start("kill", QStringList() << "-0" << QString::number(mainAppPid));
              process.waitForFinished();
              

              This?

              My approach was along the lines:

              #routines downloading and verifying update packet here 
              bool extStarted = QProcess::startDetached(params_go_here);
              #call program shutdown and exit routines here if bool is true
              

              Why would you dabble in calling QProcess to terminate self when it is safer to do so from the inside? I am leaving aside the matter of privileges required for that to succeed.

              For more information please re-read.

              Kind Regards,
              Artur

              L 1 Reply Last reply 3 Jul 2024, 08:25
              1
              • L lukutis222
                3 Jul 2024, 07:41

                @artwaw This sound like exactly my approach. Why do you think mine is different?

                J Online
                J Online
                JonB
                wrote on 3 Jul 2024, 08:12 last edited by JonB 7 Mar 2024, 08:16
                #7

                @lukutis222
                Absolutely as @artwaw has written. Additionally what is this kill program you try to run anyway? It does not come with Windows, so if you have it at all you have added it, it won't be present for your other end users. Maybe that's why it doesn't work for you either!

                L 1 Reply Last reply 3 Jul 2024, 08:26
                0
                • A artwaw
                  3 Jul 2024, 07:52

                  @lukutis222

                  QProcess process;
                  process.start("kill", QStringList() << "-0" << QString::number(mainAppPid));
                  process.waitForFinished();
                  

                  This?

                  My approach was along the lines:

                  #routines downloading and verifying update packet here 
                  bool extStarted = QProcess::startDetached(params_go_here);
                  #call program shutdown and exit routines here if bool is true
                  

                  Why would you dabble in calling QProcess to terminate self when it is safer to do so from the inside? I am leaving aside the matter of privileges required for that to succeed.

                  L Offline
                  L Offline
                  lukutis222
                  wrote on 3 Jul 2024, 08:25 last edited by lukutis222 7 Mar 2024, 08:33
                  #8

                  @artwaw

                  But that is exactly what I do after my failed attempt to kill the old process:

                      // Kill the old application
                      QProcess process;
                      process.start("kill", QStringList() << "-0" << QString::number(mainAppPid));
                      process.waitForFinished();
                  
                  
                      // Restart the application
                      qDebug() << "Restarting application";
                      QProcess::startDetached(newExecutable);
                  
                  
                      QCoreApplication::quit();
                  

                  As you can see from above, I am starting the newExecutable as new detached process which I assumed is what you mean. The only problem as I have previously mentioned is now I have 2 applications running and I want to kill the old one

                  1 Reply Last reply
                  0
                  • J JonB
                    3 Jul 2024, 08:12

                    @lukutis222
                    Absolutely as @artwaw has written. Additionally what is this kill program you try to run anyway? It does not come with Windows, so if you have it at all you have added it, it won't be present for your other end users. Maybe that's why it doesn't work for you either!

                    L Offline
                    L Offline
                    lukutis222
                    wrote on 3 Jul 2024, 08:26 last edited by lukutis222 7 Mar 2024, 08:33
                    #9

                    @JonB
                    I am trying to kill old application. The process is as following:

                    1. Run main QT application which starts updater.exe in detached mode but it does not end current application.
                    2. Updater.exe renames the old application with TestTool_renamed.exe and places new exe as TestTool.exe
                    3. Updater.exe runs the TestTool.exe as detached process
                    4. Now I have 2 applications running. I want to kill the old one

                    I am now looking into what is the best way to end the old application from the updater after it has finished swapping the files. Because as you mentioned, using kill is just not going to work. I wonder if I should be closing old application via the old application itlsef or from the updater as I am currently trying?

                    A 1 Reply Last reply 3 Jul 2024, 08:42
                    0
                    • L lukutis222
                      3 Jul 2024, 08:26

                      @JonB
                      I am trying to kill old application. The process is as following:

                      1. Run main QT application which starts updater.exe in detached mode but it does not end current application.
                      2. Updater.exe renames the old application with TestTool_renamed.exe and places new exe as TestTool.exe
                      3. Updater.exe runs the TestTool.exe as detached process
                      4. Now I have 2 applications running. I want to kill the old one

                      I am now looking into what is the best way to end the old application from the updater after it has finished swapping the files. Because as you mentioned, using kill is just not going to work. I wonder if I should be closing old application via the old application itlsef or from the updater as I am currently trying?

                      A Offline
                      A Offline
                      artwaw
                      wrote on 3 Jul 2024, 08:42 last edited by
                      #10

                      @lukutis222

                      Always via the old application itself.
                      The safest way to do so is to call close() method of the main window (which also happens to be a slot should you need it). I don't know why QCoreApplication static call doesn't work but it's not the method one would normally use anyway.

                      For more information please re-read.

                      Kind Regards,
                      Artur

                      L 1 Reply Last reply 3 Jul 2024, 08:56
                      0
                      • A artwaw
                        3 Jul 2024, 08:42

                        @lukutis222

                        Always via the old application itself.
                        The safest way to do so is to call close() method of the main window (which also happens to be a slot should you need it). I don't know why QCoreApplication static call doesn't work but it's not the method one would normally use anyway.

                        L Offline
                        L Offline
                        lukutis222
                        wrote on 3 Jul 2024, 08:56 last edited by lukutis222 7 Mar 2024, 09:06
                        #11

                        @artwaw

                        #TEST1

                        In my main application, I can close the application by calling theQCoreApplication::quit() immediately after starting detached application

                            // Start the updater process
                            if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable << QString::number(QCoreApplication::applicationPid()))) {
                                qDebug() << "Failed to start updater.";
                                return;
                            }
                        
                            QCoreApplication::quit();
                        

                        but if I do that, it does not even start updater.exe detached process which is what I am confused about. Is that expected behaviour?

                        For testing purposes, I have added 5 seconds sleep before I quite main application after starting updater:

                            // Start the updater process
                            if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable << QString::number(QCoreApplication::applicationPid()))) {
                                qDebug() << "Failed to start updater.";
                                return;
                            }
                        
                            QThread::sleep(5);
                        
                            QCoreApplication::quit();
                        

                        The result is as shown below:

                        1. Main QT application starts
                        2. It runs the detached process (updater.exe)
                        3. Updater exe performs an update and starts a new application
                        4. Now I have 2 applications running for a brief moment
                        5. After 5 seconds both applications close

                        I do not understand why does calling QCoreApplication::quit(); from the main QT application also close a new application that has been started in detached mode from updater.exe

                        #TEST2

                        Lets assume my main application will not call quit() after starting updater.exe

                        1. Main QT application starts
                        2. Starts updater.exe in detached mode
                        3. updater updates the TestTool and launches a new application
                        4. Now I have 2 applications running
                        5. Trying to manually close old application will cause both applications to close.
                        6. Trying to manually close new application will not cause old application to close.

                        I think this is where my issue lies now. Why does new application close when I try to close the old application?

                        Could that be because:
                        Since the new updated application is started from the updater and the updater is started from the main "old" application, since the updater is child of an "old" application, closing an old application means closing the updater which as a result closes new updated application because the new updated application is child of updater

                        Is that how it works?

                        A 1 Reply Last reply 3 Jul 2024, 09:58
                        0
                        • L lukutis222
                          3 Jul 2024, 08:56

                          @artwaw

                          #TEST1

                          In my main application, I can close the application by calling theQCoreApplication::quit() immediately after starting detached application

                              // Start the updater process
                              if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable << QString::number(QCoreApplication::applicationPid()))) {
                                  qDebug() << "Failed to start updater.";
                                  return;
                              }
                          
                              QCoreApplication::quit();
                          

                          but if I do that, it does not even start updater.exe detached process which is what I am confused about. Is that expected behaviour?

                          For testing purposes, I have added 5 seconds sleep before I quite main application after starting updater:

                              // Start the updater process
                              if (!QProcess::startDetached(updaterExecutable, QStringList() << oldExecutable << newExecutable << QString::number(QCoreApplication::applicationPid()))) {
                                  qDebug() << "Failed to start updater.";
                                  return;
                              }
                          
                              QThread::sleep(5);
                          
                              QCoreApplication::quit();
                          

                          The result is as shown below:

                          1. Main QT application starts
                          2. It runs the detached process (updater.exe)
                          3. Updater exe performs an update and starts a new application
                          4. Now I have 2 applications running for a brief moment
                          5. After 5 seconds both applications close

                          I do not understand why does calling QCoreApplication::quit(); from the main QT application also close a new application that has been started in detached mode from updater.exe

                          #TEST2

                          Lets assume my main application will not call quit() after starting updater.exe

                          1. Main QT application starts
                          2. Starts updater.exe in detached mode
                          3. updater updates the TestTool and launches a new application
                          4. Now I have 2 applications running
                          5. Trying to manually close old application will cause both applications to close.
                          6. Trying to manually close new application will not cause old application to close.

                          I think this is where my issue lies now. Why does new application close when I try to close the old application?

                          Could that be because:
                          Since the new updated application is started from the updater and the updater is started from the main "old" application, since the updater is child of an "old" application, closing an old application means closing the updater which as a result closes new updated application because the new updated application is child of updater

                          Is that how it works?

                          A Offline
                          A Offline
                          artwaw
                          wrote on 3 Jul 2024, 09:58 last edited by
                          #12

                          @lukutis222 This is indeed a bit peculiar and not an issue I ran into in the past. So perhaps more experienced colleagues here will have an idea? I'd add a 5 seconds delay at the start of the updater, just to make sure original program shuts down (or a flag in the file somewhere that it is closed) so the updater can wait with starting the program again - but that should not be needed in the first place...

                          For more information please re-read.

                          Kind Regards,
                          Artur

                          1 Reply Last reply
                          0
                          • H Offline
                            H Offline
                            hskoglund
                            wrote on 3 Jul 2024, 18:39 last edited by
                            #13

                            @lukutis222 you're sure you're using QProcess::startDetached() at all times? Then any spawned child(s) shouldn't die just because the parent dies. (Only exception is when you run the app from the debugger.)

                            For a quick test, create a new empty vanilla Widget app, and in the MainWindow::MainWindow constructor add:

                            QProcess::startDetached("c:/windows/notepad.exe");
                            

                            (don't forget to #include "qprocess.h" at the top)

                            If you build it (in Release mode) and start it, then Notepad should appear. Then if you kill the app from say Task Manager, Notepad should still be alive and well.

                            1 Reply Last reply
                            1
                            • L Offline
                              L Offline
                              lukutis222
                              wrote on 4 Jul 2024, 10:34 last edited by
                              #14

                              Yep you were totally right. I was testing this while running my application in debug mode. If I just simply run the application then it will work without any issues!

                              1 Reply Last reply
                              0

                              4/14

                              3 Jul 2024, 07:37

                              topic:navigator.unread, 10
                              • Login

                              • Login or register to search.
                              4 out of 14
                              • First post
                                4/14
                                Last post
                              0
                              • Categories
                              • Recent
                              • Tags
                              • Popular
                              • Users
                              • Groups
                              • Search
                              • Get Qt Extensions
                              • Unsolved