bundle agent windows_unattended_upgrade # @brief This bundle creates two scripts: one to schedule a cfengine agent upgrade # and one to actually perform the upgrade after the scripts are rendered the schedule script is run. # If the requested version and release is already installed the scripts will be deleted. { classes: windows:: "_expected_version_installed" expression => and( strcmp( $(sys.cf_version), $(cfengine_software.pkg_version) ), strcmp( $(sys.cf_version_release), $(cfengine_software.pkg_release) ) ); vars: windows:: "_schedule_upgrade_script" string => "$(const.dirsep)schedule_unattended_upgrade.ps1", comment => "sys.bindir has issues when used as execution path"; # TODO examine and fix/document specific issue(s) "_upgrade_script_name" string => "unattended-upgrade.bat"; "_upgrade_script" string => "$(sys.bindir)$(const.dirsep)$(_upgrade_script_name)"; "_scripts" slist => { "$(_schedule_upgrade_script)", "$(_upgrade_script)" }; "_min_offset" string => "2"; files: windows:: # /sigh/. In order to make a stable promise, we render a script that runs # to create the scheduled task that runs another script to perform the # upgrade outside of cf-agent. Since the command differs based on the time # of of day, the only way to have a stable promise is to promise a script # where the time base difference is handeled inside the script. One # alternative would be to use inline subshell exection (iex) "$(_schedule_upgrade_script)" create => "true", edit_template_string => ` $scheduled_time_to_upgrade = (Get-Date).AddMinutes($(_min_offset)).ToString("HH:mm") $schedule = "/SC ONCE" $taskname = '/TN "CFEngine Agent Upgrade"' $starttime = "/ST $scheduled_time_to_upgrade" $taskrun = @" "cmd /c '$(_upgrade_script)'" "@ $schtasks = "$(sys.winsysdir)$(const.dirsep)schtasks.exe" $username = '/RU "System"' $force = "/F" Invoke-Expression "$schtasks /Create $schedule $taskname /TR $taskrun $starttime $username $force" `, template_method => "inline_mustache", template_data => '{}'; "$(_upgrade_script)" edit_template_string => ` set LOGFILE=$(sys.workdir)$(const.dirsep)outputs/standalone_self_upgrade_bat.log call :SCRIPT > "%LOGFILE%" 2>&1 exit /B :SCRIPT REM kill all cf procs first so that installed can run Taskkill /IM cf-execd.exe /F Taskkill /IM cf-monitord.exe /F Taskkill /IM cf-serverd.exe /F Taskkill /IM cf-agent.exe /F set bat="$(sys.workdir)$(const.dirsep)modules$(const.dirsep)packages$(const.dirsep)msiexec.bat" echo File=$(cfengine_software.local_software_dir)$(const.dirsep)$(cfengine_package_names.my_pkg) | call %bat% file-install @echo off REM A failed install at this point can leave cf-execd not running leaving the host unable to try and self heal REM We at least want to try and start cf-execd if it is not running set TARGET_SERVICE=CfengineNovaExec set SERVICE_STATE= REM Surgically target third line, as some locales (such as Spanish) translate the output for /F "skip=3 tokens=3" %%i in ('""%windir%\system32\sc.exe" query "%TARGET_SERVICE%" 2>nul"') do ( if not defined SERVICE_STATE set SERVICE_STATE=%%i ) rem Process result if not defined SERVICE_STATE ( echo ERROR: could not obtain service state! ) else ( REM NOTE: values correspond to "SERVICE_STATUS.dwCurrentState" REM https://msdn.microsoft.com/en-us/library/windows/desktop/ms685996(v=vs.85).aspx if not %SERVICE_STATE%==4 ( echo WARNING: service is not running, attempting to start net start "%TARGET_SERVICE%" ) else ( echo INFORMATION: service is running ) ) REM exit 0 so that scheduled task will not keep trying a failing situation REM looking at package module logs of one failure should help in debugging exit 0 `, template_method => "inline_mustache", template_data => '{}', create => "true", if => not( "_expected_version_installed" ); "$(_scripts)" delete => windows_unattended_upgrade_tidy, if => "_expected_version_installed"; commands: windows:: 'powershell -NoProfile -ExecutionPolicy bypass -F $(_schedule_upgrade_script)' if => not( "_expected_version_installed" ), contain => windows_unattended_upgrade:powershell; } body delete windows_unattended_upgrade_tidy { dirlinks => "delete"; rmdirs => "true"; } body file control { namespace => "windows_unattended_upgrade"; } body contain powershell { useshell => "powershell"; }