/// <summary> /// Performs software updates. /// </summary> /// <param name="status">software status, as retrieved via SoftwareStatus.query()</param> /// <param name="timeoutPerUpdate">maximum time in seconds to wait per update</param> /// <returns>Returns the number of updated application in case of success. /// Returns a negative value equal to -1 - number of updated applications, if an error occurred.</returns> public static int update(List <QueryEntry> status, uint timeoutPerUpdate = defaultTimeout) { if (null == status) { return(-1); } if (status.Count == 0) { logger.Info("No known software was found, so no update will be performed."); return(-1); } // set some reasonable timeout, if necessary if (timeoutPerUpdate <= minimumTimeout) { timeoutPerUpdate = defaultTimeout; } int updatedApplications = 0; foreach (var entry in status) { if (!entry.needsUpdate) { continue; } InstallInfo instInfo = null; switch (entry.type) { case ApplicationType.Bit32: instInfo = entry.software.info().install32Bit; break; case ApplicationType.Bit64: instInfo = entry.software.info().install64Bit; break; case ApplicationType.Unknown: logger.Warn("Warning: Unknown application type detected for " + entry.software.info().Name + "! Update will be skipped."); continue; default: logger.Warn("Warning: Unknown application type detected for " + entry.software.info().Name + "! Update will be aborted."); return(-1 - updatedApplications); } // If no verification method is provided, we do not even try to download the file. if (!instInfo.canBeVerified()) { logger.Error("Error: No checksum and no signature information for download of " + entry.software.info().Name + " is available!"); logger.Error("Since installing unverified software can" + " pose a security thread to your system, the update is cancelled."); return(-1 - updatedApplications); } // check for blocking processes if (utility.Processes.processesExist(entry.software.blockerProcesses(entry.detected))) { logger.Warn("Warning: At least one process was found that " + "blocks the update of " + entry.software.info().Name + "! Update will be omitted."); continue; } // download file if (string.IsNullOrWhiteSpace(instInfo.downloadUrl)) { logger.Error("Error: No known download URL for " + entry.software.info().Name + "!"); return(-1 - updatedApplications); } logger.Info("Downloading " + instInfo.downloadUrl + "..."); string downloadedFile = download(instInfo.downloadUrl); if (string.IsNullOrWhiteSpace(downloadedFile)) { logger.Error("Error: Could not download installer from " + instInfo.downloadUrl + "!"); return(-1 - updatedApplications); } // file verification bool verifiedChecksum = false; bool verifiedSignature = false; // checksum verification if (instInfo.hasChecksum()) { // calculate checksum logger.Info("Calculating checksum of " + downloadedFile + " ..."); string hash = utility.Checksum.calculate(downloadedFile, instInfo.algorithm); if (string.IsNullOrWhiteSpace(hash)) { logger.Error("Error: Could not calculate checksum of file " + downloadedFile + "!"); File.Delete(downloadedFile); return(-1 - updatedApplications); } if (!utility.Checksum.areEqual(hash, instInfo.checksum)) { logger.Error("Error: Checksum of file " + downloadedFile + " is " + hash + ", but expected checksum is " + instInfo.checksum + "!"); File.Delete(downloadedFile); return(-1 - updatedApplications); } logger.Info("Info: Checksum of " + downloadedFile + " is correct."); verifiedChecksum = true; } // if checksum // signature verification if (instInfo.hasVerifiableSignature()) { logger.Info("Verifying signature of " + downloadedFile + " ..."); if (!utility.Verificator.verifySignature(downloadedFile, instInfo.signature.publisher)) { logger.Error("Error: Signature of file " + downloadedFile + " is invalid or missing! The file may also have the wrong publisher."); File.Delete(downloadedFile); return(-1 - updatedApplications); } logger.Info("Info: Signature and publisher of " + downloadedFile + " are correct."); verifiedSignature = true; } // if signature if (!verifiedChecksum && !verifiedSignature) { logger.Error("Error: Downloaded file " + downloadedFile + " could not be verified to be authentic!"); logger.Error("Since installing unverified software can" + " pose a security thread to your system, the update is cancelled."); File.Delete(downloadedFile); return(-1 - updatedApplications); } // Check for blocking processes - again, because download can take // enough time to start some new processes. if (utility.Processes.processesExist(entry.software.blockerProcesses(entry.detected))) { logger.Warn("Warning: At least one process was found that " + "blocks the update of " + entry.software.info().Name + "! Update will be omitted."); File.Delete(downloadedFile); continue; } // start update process try { // preparational process needed? if (entry.software.needsPreUpdateProcess(entry.detected)) { var preProcs = entry.software.preUpdateProcess(entry.detected); if (null == preProcs) { logger.Error("Error: Pre-update process for " + entry.software.info().Name + " is null!"); return(-1 - updatedApplications); } foreach (System.Diagnostics.Process preProc in preProcs) { logger.Info("Info: Starting pre-update task for " + entry.software.info().Name + "..."); try { preProc.Start(); uint intervalCounter = 0; do { System.Threading.Thread.Sleep(1000); ++intervalCounter; if (preProc.HasExited) { logger.Info("Info: Pre-update process exited after " + intervalCounter.ToString() + " second(s) with code " + preProc.ExitCode.ToString() + "."); break; } // only wait up to timeoutPerUpdate seconds } while (intervalCounter <= timeoutPerUpdate); bool success = preProc.HasExited && (preProc.ExitCode == 0); // Kill it, if it is not done yet. if (!preProc.HasExited) { logger.Error("Error: Killing pre-update process, because timeout has been reached."); preProc.Kill(); return(-1 - updatedApplications); } if (!success) { if (!entry.software.allowPreUpdateProcessFailure(entry.detected, preProc)) { logger.Error("Error: Could not perform pre-update task for " + entry.software.info().Name + "."); return(-1 - updatedApplications); } else { logger.Warn("Info: Pre-update task for " + entry.software.info().Name + " failed, but that is allowed."); } } } // try-c catch (Exception ex) { logger.Error("Error: An exception occurred while running a pre-update tast for " + entry.software.info().Name + ": " + ex.Message); return(-1 - updatedApplications); } } // foreach } // if preparational process is needed var proc = instInfo.createInstallProccess(downloadedFile, entry.detected); if (null == proc) { // error while creating install process - should never happen logger.Error("Error: Could not create install process for " + entry.software.info().Name + "!"); return(-1 - updatedApplications); } try { logger.Info("Info: Starting update of " + entry.software.info().Name + "..."); logger.Debug("Command line: " + proc.StartInfo.FileName + " " + proc.StartInfo.Arguments); bool startedNew = proc.Start(); uint intervalCounter = 0; do { System.Threading.Thread.Sleep(1000); ++intervalCounter; if (proc.HasExited) { logger.Info("Info: Update process exited after " + intervalCounter.ToString() + " second(s) with code " + proc.ExitCode.ToString() + "."); break; } // only wait up to timeoutPerUpdate seconds } while (intervalCounter <= timeoutPerUpdate); // Update was successful, if process has exited already, // i.e. there was no timeout. // Additionally, the exit code must be zero. // However, for MSI processes the exit code 3010 means // the the update succeeded, but a reboot is required. bool success = proc.HasExited && ((proc.ExitCode == 0) || ((proc.ExitCode == InstallInfoMsi.successRebootRequired) && (instInfo is InstallInfoMsi))); // Kill it, if it is not done yet. if (!proc.HasExited) { logger.Warn("Warning: Killing update process, because timeout has been reached."); proc.Kill(); } if (success) { logger.Info("Info: Update of " + entry.software.info().Name + " was successful."); ++updatedApplications; if ((instInfo is InstallInfoMsi) && (proc.ExitCode == InstallInfoMsi.successRebootRequired)) { logger.Warn("Warning: A reboot is required to" + " finish the update of " + entry.software.info().Name + "."); } // if MSI installer requires reboot } // if success else { logger.Error("Error: Could not update " + entry.software.info().Name + "."); return(-1 - updatedApplications); } } // try-c catch (Exception ex) { logger.Error("Error: Exception occurred while updating " + entry.software.info().Name + ": " + ex.Message); return(-1 - updatedApplications); } // try-catch } // try-fin finally { try { File.Delete(downloadedFile); } catch (Exception ex) { logger.Error("Error: Could not delete installer file " + downloadedFile + " after update: " + ex.Message); } } // try-finally } // foreach return(updatedApplications); }