From b46bcea27010c08fbc866590b5555cdd908e6818 Mon Sep 17 00:00:00 2001 From: Jamie Hall Date: Wed, 21 May 2025 20:30:15 +0100 Subject: [PATCH 01/49] Random Delay Added in scheduled task random delay for scheduled task time triggers. Added in admx changes. --- Sources/Policies/ADMX/WAU.admx | 12 ++++++++++ Sources/Policies/ADMX/en-US/WAU.adml | 15 ++++++++++++ Sources/Winget-AutoUpdate/WAU-Policies.ps1 | 28 +++++++++++++++++----- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/Sources/Policies/ADMX/WAU.admx b/Sources/Policies/ADMX/WAU.admx index 6ab2d2e8d..11f2b8330 100644 --- a/Sources/Policies/ADMX/WAU.admx +++ b/Sources/Policies/ADMX/WAU.admx @@ -10,6 +10,7 @@ + @@ -420,5 +421,16 @@ + + + + + + + \ No newline at end of file diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index 0a85f7dbf..f0c2daf2d 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -10,6 +10,7 @@ Experimental Winget-AutoUpdate version 1.16.0 or later Winget-AutoUpdate version 1.16.5 or later + Winget-AutoUpdate version 2.5.2 or later Winget-AutoUpdate Experimental, subject to change, do not use on PROD Activate WAU GPO Management @@ -185,6 +186,15 @@ If this policy is disabled or not configured, the default is always the built-in winget. + Random delay for scheduled task triggers + This policy setting specifies the delay for the scheduled task. + A scheduled task random delay adds a random amount of wait time (up to the specified maximum) before the task starts. + This helps prevent many devices from running the task at the exact same time. This is not applicable to "on logon" triggers. + + If this policy is enabled, the scheduled task will have a random delay set + based on the time inputted. + + If this policy is disabled or not configured, the default no delay. @@ -232,6 +242,11 @@ + + + + + \ No newline at end of file diff --git a/Sources/Winget-AutoUpdate/WAU-Policies.ps1 b/Sources/Winget-AutoUpdate/WAU-Policies.ps1 index e1276ddf7..1c263f01f 100644 --- a/Sources/Winget-AutoUpdate/WAU-Policies.ps1 +++ b/Sources/Winget-AutoUpdate/WAU-Policies.ps1 @@ -87,9 +87,25 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { $configChanged = $true } + #Check if delay is set + if ($WAUConfig.WAU_UpdatesTimeDelay) { + $randomDelay = [TimeSpan]::ParseExact($WAUConfig.WAU_UpdatesTimeDelay, "hh\:mm", $null) + } else { + $randomDelay = [TimeSpan]::ParseExact("00:00", "hh\:mm", $null) #setting to 00:00 disables the random delay + } + + #Check if delay has changed + $timeTrigger = $currentTriggers | Where-Object { $_.CimClass.CimClassName -ne "MSFT_TaskLogonTrigger" } | Select-Object -First 1 + if ($timeTrigger.RandomDelay -match '^PT(?:(\d+)H)?(?:(\d+)M)?$') { + $hours = if ($matches[1]) { [int]$matches[1] } else { 0 } + $minutes = if ($matches[2]) { [int]$matches[2] } else { 0 } + $existingRandomDelay = New-TimeSpan -Hours $hours -Minutes $minutes + } + if ($existingRandomDelay -ne $randomDelay) { + $configChanged = $true + } #Check if schedule time has changed if ($currentIntervalType -ne "None" -and $currentIntervalType -ne "Never") { - $timeTrigger = $currentTriggers | Where-Object { $_.CimClass.CimClassName -ne "MSFT_TaskLogonTrigger" } | Select-Object -First 1 if ($timeTrigger) { $currentTime = [DateTime]::Parse($timeTrigger.StartBoundary).ToString("HH:mm:ss") if ($currentTime -ne $WAUConfig.WAU_UpdatesAtTime) { @@ -105,19 +121,19 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { $tasktriggers += New-ScheduledTaskTrigger -AtLogOn } if ($WAUConfig.WAU_UpdatesInterval -eq "Daily") { - $tasktriggers += New-ScheduledTaskTrigger -Daily -At $WAUConfig.WAU_UpdatesAtTime + $tasktriggers += New-ScheduledTaskTrigger -Daily -At $WAUConfig.WAU_UpdatesAtTime -RandomDelay $randomDelay } elseif ($WAUConfig.WAU_UpdatesInterval -eq "BiDaily") { - $tasktriggers += New-ScheduledTaskTrigger -Daily -At $WAUConfig.WAU_UpdatesAtTime -DaysInterval 2 + $tasktriggers += New-ScheduledTaskTrigger -Daily -At $WAUConfig.WAU_UpdatesAtTime -DaysInterval 2 -RandomDelay $randomDelay } elseif ($WAUConfig.WAU_UpdatesInterval -eq "Weekly") { - $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 + $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 -RandomDelay $randomDelay } elseif ($WAUConfig.WAU_UpdatesInterval -eq "BiWeekly") { - $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 -WeeksInterval 2 + $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 -WeeksInterval 2 -RandomDelay $randomDelay } elseif ($WAUConfig.WAU_UpdatesInterval -eq "Monthly") { - $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 -WeeksInterval 4 + $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 -WeeksInterval 4 -RandomDelay $randomDelay } #If trigger(s) set From 25433f88773e760e1fe236037d46db2c921c8d93 Mon Sep 17 00:00:00 2001 From: Jamie Hall Date: Sun, 25 May 2025 19:06:55 +0100 Subject: [PATCH 02/49] Fix to wxs --- Sources/Policies/ADMX/WAU.admx | 2 +- Sources/Winget-AutoUpdate/WAU-Policies.ps1 | 8 +------- Sources/Wix/build.wxs | 8 ++++++++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/Policies/ADMX/WAU.admx b/Sources/Policies/ADMX/WAU.admx index 11f2b8330..3f44842eb 100644 --- a/Sources/Policies/ADMX/WAU.admx +++ b/Sources/Policies/ADMX/WAU.admx @@ -1,6 +1,6 @@ diff --git a/Sources/Winget-AutoUpdate/WAU-Policies.ps1 b/Sources/Winget-AutoUpdate/WAU-Policies.ps1 index 1c263f01f..fb12725c3 100644 --- a/Sources/Winget-AutoUpdate/WAU-Policies.ps1 +++ b/Sources/Winget-AutoUpdate/WAU-Policies.ps1 @@ -87,14 +87,8 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { $configChanged = $true } - #Check if delay is set - if ($WAUConfig.WAU_UpdatesTimeDelay) { - $randomDelay = [TimeSpan]::ParseExact($WAUConfig.WAU_UpdatesTimeDelay, "hh\:mm", $null) - } else { - $randomDelay = [TimeSpan]::ParseExact("00:00", "hh\:mm", $null) #setting to 00:00 disables the random delay - } - #Check if delay has changed + $randomDelay = [TimeSpan]::ParseExact($WAUConfig.WAU_UpdatesTimeDelay, "hh\:mm", $null) $timeTrigger = $currentTriggers | Where-Object { $_.CimClass.CimClassName -ne "MSFT_TaskLogonTrigger" } | Select-Object -First 1 if ($timeTrigger.RandomDelay -match '^PT(?:(\d+)H)?(?:(\d+)M)?$') { $hours = if ($matches[1]) { [int]$matches[1] } else { 0 } diff --git a/Sources/Wix/build.wxs b/Sources/Wix/build.wxs index bb0f964e7..bd6ae3e8d 100644 --- a/Sources/Wix/build.wxs +++ b/Sources/Wix/build.wxs @@ -83,6 +83,10 @@ + + + + @@ -257,6 +261,7 @@ + @@ -330,6 +335,9 @@ + + + From 792a9efed7f75da234a4b3ad2bdf44e5e57fe8e7 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Tue, 27 May 2025 17:42:33 +0200 Subject: [PATCH 03/49] 2_6_0 --- Sources/Policies/ADMX/WAU.admx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Policies/ADMX/WAU.admx b/Sources/Policies/ADMX/WAU.admx index 3f44842eb..3a4359aa1 100644 --- a/Sources/Policies/ADMX/WAU.admx +++ b/Sources/Policies/ADMX/WAU.admx @@ -10,7 +10,7 @@ - + @@ -427,10 +427,10 @@ key="Software\Policies\Romanitho\Winget-AutoUpdate" presentation="$(presentation.UpdatesTimeDelayTime)"> - + - \ No newline at end of file + From de34b546d228818963afd0227e28f53ff5af98ef Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Tue, 27 May 2025 17:43:10 +0200 Subject: [PATCH 04/49] 2.6.0 --- Sources/Policies/ADMX/en-US/WAU.adml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index f0c2daf2d..b29e47118 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -10,7 +10,7 @@ Experimental Winget-AutoUpdate version 1.16.0 or later Winget-AutoUpdate version 1.16.5 or later - Winget-AutoUpdate version 2.5.2 or later + Winget-AutoUpdate version 2.6.0 or later Winget-AutoUpdate Experimental, subject to change, do not use on PROD Activate WAU GPO Management @@ -249,4 +249,4 @@ - \ No newline at end of file + From 25b39207c8b14da36a49a49c313d906b15102111 Mon Sep 17 00:00:00 2001 From: Andrzej Demski <75534654+AndrewDemski-ad-gmail-com@users.noreply.github.com> Date: Sat, 14 Jun 2025 20:51:55 +0200 Subject: [PATCH 05/49] Update README.md Updated Readme with new "UPDATESATTIMEDELAY" property https://github.com/Romanitho/Winget-AutoUpdate/issues/964 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1ee1451bf..2cf522c49 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,11 @@ Default value Never. Specify the update frequency: Daily, BiDaily, Weekly, BiWee ### UPDATESATTIME Default value 6AM (06:00:00). Specify the time of the update interval execution time. Example `UPDATESATTIME="11:00:00"` +### UPDATESATTIMEDELAY +Default value is none (00:00). This setting specifies the delay for the scheduled task. +A scheduled task random delay adds a random amount of wait time (up to the specified maximum) before the task starts. +This helps prevent many devices from running the task at the exact same time. This is not applicable to "on logon" triggers. + ### DONOTRUNONMETERED Default value 1. Set `DONOTRUNONMETERED=0` to force WAU to run on metered connections. May add cellular data costs on shared connexion from smartphone for example. From 6c3a4e8535166f2a9d7a0280e23afdd7ca199caf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 06:09:12 +0000 Subject: [PATCH 06/49] Bump stefanzweifel/git-auto-commit-action from 5.2.0 to 6.0.1 Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5.2.0 to 6.0.1. - [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases) - [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/b863ae1933cb653a53c021fe36dbb774e1fb9403...778341af668090896ca464160c2def5d1d1a3eb0) --- updated-dependencies: - dependency-name: stefanzweifel/git-auto-commit-action dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/GA_Mega-linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GA_Mega-linter.yml b/.github/workflows/GA_Mega-linter.yml index 42fd86c4a..952f11cc7 100644 --- a/.github/workflows/GA_Mega-linter.yml +++ b/.github/workflows/GA_Mega-linter.yml @@ -90,7 +90,7 @@ jobs: run: sudo chown -Rc $UID .git/ - name: Commit and push applied linter fixes if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) && !contains(github.event.head_commit.message, 'skip fix') - uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0 + uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1 with: branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }} commit_message: "[MegaLinter] Apply linters fixes" From 003227215524c81dc26f8fff89b6732bffe413f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 06:14:06 +0000 Subject: [PATCH 07/49] Bump oxsecurity/megalinter from 8.7.0 to 8.8.0 Bumps [oxsecurity/megalinter](https://github.com/oxsecurity/megalinter) from 8.7.0 to 8.8.0. - [Release notes](https://github.com/oxsecurity/megalinter/releases) - [Changelog](https://github.com/oxsecurity/megalinter/blob/main/CHANGELOG.md) - [Commits](https://github.com/oxsecurity/megalinter/compare/5a91fb06c83d0e69fbd23756d47438aa723b4a5a...e08c2b05e3dbc40af4c23f41172ef1e068a7d651) --- updated-dependencies: - dependency-name: oxsecurity/megalinter dependency-version: 8.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/GA_Mega-linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GA_Mega-linter.yml b/.github/workflows/GA_Mega-linter.yml index 42fd86c4a..57dffc625 100644 --- a/.github/workflows/GA_Mega-linter.yml +++ b/.github/workflows/GA_Mega-linter.yml @@ -43,7 +43,7 @@ jobs: id: ml # You can override MegaLinter flavor used to have faster performances # More info at https://megalinter.github.io/flavors/ - uses: oxsecurity/megalinter@5a91fb06c83d0e69fbd23756d47438aa723b4a5a # v8.7.0 + uses: oxsecurity/megalinter@e08c2b05e3dbc40af4c23f41172ef1e068a7d651 # v8.8.0 env: # All available variables are described in documentation # https://megalinter.github.io/configuration/ From 27e25c53bd3f4d3a738b21580ea49914e6f49f37 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sat, 21 Jun 2025 10:53:31 +0200 Subject: [PATCH 08/49] Enhance deployment instructions and mod functionality in README and scripts - Updated README.md to include deployment examples for SCCM and Intune. - Improved Winget-Install.ps1 to handle mod overrides and custom parameters. - Enhanced Update-App.ps1 to support pre-install checks and improved logging. - Added functionality to skip apps based on running processes in mod templates. - Introduced new functions for process management in _Mods-Functions.ps1. - Implemented winget-detect.ps1 for detecting installed applications. --- README.md | 35 +++- Sources/Winget-AutoUpdate/Winget-Install.ps1 | 158 ++++++++---------- .../config/winget-detect.ps1 | 53 ++++++ .../functions/Update-App.ps1 | 126 ++++++++++---- Sources/Winget-AutoUpdate/mods/README.md | 4 +- .../mods/_AppID-template.ps1 | 8 + .../mods/_Mods-Functions.ps1 | 10 ++ 7 files changed, 262 insertions(+), 132 deletions(-) create mode 100644 Sources/Winget-AutoUpdate/config/winget-detect.ps1 diff --git a/README.md b/README.md index 1ee1451bf..2efbb0418 100644 --- a/README.md +++ b/README.md @@ -175,10 +175,25 @@ Default is 1048576 = 1 MB (ca. 7500 lines) Specify Winget-AutoUpdate installation location. Default: `C:\Program Files\Winget-AutoUpdate` (Recommended to leave default). ### Deploy with Intune -You can use [Winget-Install](https://github.com/Romanitho/Winget-AutoUpdate/blob/main/Sources/Winget-AutoUpdate/Winget-Install.ps1) to deploy the package for example in Intune: +You can use [Winget-Install](https://github.com/Romanitho/Winget-AutoUpdate/blob/main/Sources/Winget-AutoUpdate/Winget-Install.ps1) to deploy the package (this example with an override of parameters): ```batch -"%systemroot%\sysnative\WindowsPowerShell\v1.0\powershell.exe" -noprofile -executionpolicy bypass -file "C:\Program Files\Winget-AutoUpdate\Winget-Install.ps1" -AppIDs "Romanitho.Winget-AutoUpdate --scope machine --override \"/qn RUN_WAU=YES USERCONTEXT=1 STARTMENUSHORTCUT=1 NOTIFICATIONLEVEL=SuccessOnly UPDATESINTERVAL=Daily"" +"%systemroot%\sysnative\WindowsPowerShell\v1.0\powershell.exe" -noprofile -executionpolicy bypass -file "C:\Program Files\Winget-AutoUpdate\Winget-Install.ps1" -AppIDs "Adobe.Acrobat.Reader.64-bit --scope machine --override \"-sfx_nu /sAll /rs /msi EULA_ACCEPT=YES DISABLEDESKTOPSHORTCUT=1"" ``` +### Deploy with SCCM +You can also use [Winget-Install](https://github.com/Romanitho/Winget-AutoUpdate/blob/main/Sources/Winget-AutoUpdate/Winget-Install.ps1) to deploy the same package in **SCCM**: +```batch +powershell.exe -noprofile -executionpolicy bypass -file "C:\Program Files\Winget-AutoUpdate\Winget-Install.ps1" -AppIDs "Adobe.Acrobat.Reader.64-bit --scope machine --override \"-sfx_nu /sAll /rs /msi EULA_ACCEPT=YES DISABLEDESKTOPSHORTCUT=1"" +``` +Instead of including the override parameters in the install string you can use a **Mod** (**mods\Adobe.Acrobat.Reader.64-bit-override.txt**) with the content: +```batch +"-sfx_nu /sAll /rs /msi EULA_ACCEPT=YES DISABLEDESKTOPSHORTCUT=1" +``` +* A standard single installation: **-AppIDs Notepad++.Notepad++** +* Multiple installations: **-AppIDs "7zip.7zip, Notepad++.Notepad++"** + +As a detection script use **config\winget-detect.ps1** (change app to detect [**Application ID**]) in **Intune**/**SCCM** ([winget-detect.ps1](Sources/Winget-AutoUpdate/config/winget-detect.ps1)) + +A nice feature is if you're already using the deprecated standalone script **winget-install.ps1** from the [old repo](https://github.com/Romanitho/Winget-Install) and have placed it somwhere locally on all clients you can make a **SymLink** in its place and keep using the old path (avoiding a lot of work) in your deployed applications (**Winget-Install.ps1** takes care of the SymLink logic). ## GPO / Intune Management Read more in the [Policies section](https://github.com/Romanitho/Winget-AutoUpdate/tree/main/Sources/Policies). @@ -191,6 +206,8 @@ If **ExitCode** is **1** from `_WAU-mods.ps1` then **Re-run WAU**. Likewise `_WAU-mods-postsys.ps1` can be used to do things at the end of the **SYSTEM context WAU** process before the user run. +You can find more information in [README Mods for WAU](Sources/Winget-AutoUpdate/mods/README.md) + ## Custom scripts (Mods feature for Apps) The Mods feature allows you to run additional scripts when upgrading or installing an app. Just put the scripts in question with the **AppID** followed by the `-preinstall`, `-upgrade`, `-install`, `-installed` or `-notinstalled` suffix in the **mods** folder. @@ -207,8 +224,7 @@ The **-install** mod will be used for upgrades too if **-upgrade** doesn't exist > Example:
If you want to run a script that removes the shortcut from **%PUBLIC%\Desktop** (we don't want to fill the desktop with shortcuts our users can't delete) just after installing **Acrobat Reader DC** (32-bit), prepare a powershell script that removes the Public Desktop shortcut **Acrobat Reader DC.lnk** and name your script like this: `Adobe.Acrobat.Reader.32-bit-installed.ps1` and put it in the **mods** folder. -You can find more information on [Winget-Install Repo](https://github.com/Romanitho/Winget-AutoUpdate?tab=readme-ov-file#custom-script-mods-for-wau), as it's a related feature.
-Read more in the `README.md` under the directory **mods**. +You can find more information in [README Mods for WAU](Sources/Winget-AutoUpdate/mods/README.md), as it's a related feature. Share your mods with the community:
@@ -216,9 +232,16 @@ Share your mods with the community:
### Winget native parameters Another finess is the **AppID** followed by the `-override` suffix as a **text file** (.**txt**) that you can place under the **mods** folder. > Example:
-**Canneverbe.CDBurnerXP-override.txt** with the content `ADDLOCAL=All REMOVE=Desktop_Shortcut /qn` +**Adobe.Acrobat.Reader.64-bit-override.txt** with the content `"-sfx_nu /sAll /rs /msi EULA_ACCEPT=YES DISABLEDESKTOPSHORTCUT=1"` + +This will use the **content** of the text file as a native **winget --override** parameter when upgrading. + +Likewise you can use the **AppID** followed by the `-custom` suffix as a **text file** (.**txt**) that you can place under the **mods** folder (*Arguments to be passed on to the installer in addition to the defaults*). +> Example:
+**Adobe.Acrobat.Reader.64-bit-custom.txt** with the content `"DISABLEDESKTOPSHORTCUT=1"` + +This will use the **content** of the text file as a native **winget --custom** parameter when upgrading. -This will use the **content** of the text file as a native **winget --override** parameter when upgrading (as proposed by [JonNesovic](https://github.com/JonNesovic) in [Mod for --override argument #244](https://github.com/Romanitho/Winget-AutoUpdate/discussions/244#discussion-4637666)). ## Known issues * As reported by [soredake](https://github.com/soredake), Powershell from MsStore is not supported with WAU in system context. See diff --git a/Sources/Winget-AutoUpdate/Winget-Install.ps1 b/Sources/Winget-AutoUpdate/Winget-Install.ps1 index 84d34324b..985870507 100644 --- a/Sources/Winget-AutoUpdate/Winget-Install.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Install.ps1 @@ -1,11 +1,11 @@ <# .SYNOPSIS Install apps with Winget through Intune or SCCM. -Can be used standalone. +(Can be used standalone.) - Deprecated in favor of Winget-AutoUpdate. .DESCRIPTION Allow to run Winget in System Context to install your apps. -https://github.com/Romanitho/Winget-Install +(https://github.com/Romanitho/Winget-Install) - Deprecated in favor of Winget-AutoUpdate. .PARAMETER AppIDs Forward Winget App ID to install. For multiple apps, separate with ",". Case sensitive. @@ -90,59 +90,39 @@ function Confirm-Exist ($AppID) { #Check if install modifications exist in "mods" directory function Test-ModsInstall ($AppID) { - #Check current location - if (Test-Path ".\mods\$AppID-preinstall.ps1") { - $ModsPreInstall = ".\mods\$AppID-preinstall.ps1" - } - #Else, check in WAU mods - elseif (Test-Path "$WAUModsLocation\$AppID-preinstall.ps1") { - $ModsPreInstall = "$WAUModsLocation\$AppID-preinstall.ps1" - } - - if (Test-Path ".\mods\$AppID-install.ps1") { - $ModsInstall = ".\mods\$AppID-install.ps1" - } - elseif (Test-Path "$WAUModsLocation\$AppID-install.ps1") { - $ModsInstall = "$WAUModsLocation\$AppID-install.ps1" - } - - if (Test-Path ".\mods\$AppID-installed-once.ps1") { - $ModsInstalledOnce = ".\mods\$AppID-installed-once.ps1" - } - - if (Test-Path ".\mods\$AppID-installed.ps1") { - $ModsInstalled = ".\mods\$AppID-installed.ps1" - } - elseif (Test-Path "$WAUModsLocation\$AppID-installed.ps1") { - $ModsInstalled = "$WAUModsLocation\$AppID-installed.ps1" + if (Test-Path "$Mods\$AppID-*") { + if (Test-Path "$Mods\$AppID-preinstall.ps1") { + $ModsPreInstall = "$Mods\$AppID-preinstall.ps1" + } + if (Test-Path "$Mods\$AppID-override.txt") { + $ModsOverride = (Get-Content "$Mods\$AppID-override.txt" -Raw).Trim() + } + if (Test-Path "$Mods\$AppID-custom.txt") { + $ModsCustom = (Get-Content "$Mods\$AppID-custom.txt" -Raw).Trim() + } + if (Test-Path "$Mods\$AppID-install.ps1") { + $ModsInstall = "$Mods\$AppID-install.ps1" + } + if (Test-Path "$Mods\$AppID-installed.ps1") { + $ModsInstalled = "$Mods\$AppID-installed.ps1" + } } - return $ModsPreInstall, $ModsInstall, $ModsInstalledOnce, $ModsInstalled + return $ModsPreInstall, $ModsOverride, $ModsCustom, $ModsInstall, $ModsInstalled } #Check if uninstall modifications exist in "mods" directory function Test-ModsUninstall ($AppID) { - #Check current location - if (Test-Path ".\mods\$AppID-preuninstall.ps1") { - $ModsPreUninstall = ".\mods\$AppID-preuninstall.ps1" - } - #Else, check in WAU mods - elseif (Test-Path "$WAUModsLocation\$AppID-preuninstall.ps1") { - $ModsPreUninstall = "$WAUModsLocation\$AppID-preuninstall.ps1" - } - - if (Test-Path ".\mods\$AppID-uninstall.ps1") { - $ModsUninstall = ".\mods\$AppID-uninstall.ps1" - } - elseif (Test-Path "$WAUModsLocation\$AppID-uninstall.ps1") { - $ModsUninstall = "$WAUModsLocation\$AppID-uninstall.ps1" - } - - if (Test-Path ".\mods\$AppID-uninstalled.ps1") { - $ModsUninstalled = ".\mods\$AppID-uninstalled.ps1" - } - elseif (Test-Path "$WAUModsLocation\$AppID-uninstalled.ps1") { - $ModsUninstalled = "$WAUModsLocation\$AppID-uninstalled.ps1" + if (Test-Path "$Mods\$AppID-*") { + if (Test-Path "$Mods\$AppID-preuninstall.ps1") { + $ModsPreUninstall = "$Mods\$AppID-preuninstall.ps1" + } + if (Test-Path "$Mods\$AppID-uninstall.ps1") { + $ModsUninstall = "$Mods\$AppID-uninstall.ps1" + } + if (Test-Path "$Mods\$AppID-uninstalled.ps1") { + $ModsUninstalled = "$Mods\$AppID-uninstalled.ps1" + } } return $ModsPreUninstall, $ModsUninstall, $ModsUninstalled @@ -152,23 +132,38 @@ function Test-ModsUninstall ($AppID) { function Install-App ($AppID, $AppArgs) { $IsInstalled = Confirm-Installation $AppID if (!($IsInstalled) -or $AllowUpgrade ) { - #Check if mods exist (or already exist) for preinstall/install/installedonce/installed - $ModsPreInstall, $ModsInstall, $ModsInstalledOnce, $ModsInstalled = Test-ModsInstall $($AppID) + #Check if mods exist (or already exist) for preinstall/override/custom/install/installed + $ModsPreInstall, $ModsOverride, $ModsCustom, $ModsInstall, $ModsInstalled = Test-ModsInstall $($AppID) #If PreInstall script exist if ($ModsPreInstall) { - Write-ToLog "-> Modifications for $AppID before install are being applied..." "Yellow" - & "$ModsPreInstall" + Write-ToLog "Modifications for $AppID before install are being applied..." "DarkYellow" + $preInstallResult = & "$ModsPreInstall" + if ($preInstallResult -eq $false) { + Write-ToLog "PreInstall script for $AppID requested to skip this installation" "Yellow" + return # Exit the function early + } } #Install App - Write-ToLog "-> Installing $AppID..." "Yellow" - $WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget -h $AppArgs" -split " " + Write-ToLog "-> Installing $AppID..." "DarkYellow" + if ($ModsOverride) { + Write-ToLog "-> Arguments (overriding default): $ModsOverride" # Without -h (user overrides default) + $WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget --override $ModsOverride" -split " " + } + elseif ($ModsCustom) { + Write-ToLog "-> Arguments (customizing default): $ModsCustom" # With -h (user customizes default) + $WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget -h --custom $ModsCustom" -split " " + } + else { + $WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget -h $AppArgs" -split " " + } + Write-ToLog "-> Running: `"$Winget`" $WingetArgs" - & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append + & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append if ($ModsInstall) { - Write-ToLog "-> Modifications for $AppID during install are being applied..." "Yellow" + Write-ToLog "-> Modifications for $AppID during install are being applied..." "DarkYellow" & "$ModsInstall" } @@ -177,26 +172,11 @@ function Install-App ($AppID, $AppArgs) { if ($IsInstalled) { Write-ToLog "-> $AppID successfully installed." "Green" - if ($ModsInstalledOnce) { - Write-ToLog "-> Modifications for $AppID after install (one time) are being applied..." "Yellow" - & "$ModsInstalledOnce" - } - elseif ($ModsInstalled) { - Write-ToLog "-> Modifications for $AppID after install are being applied..." "Yellow" + if ($ModsInstalled) { + Write-ToLog "-> Modifications for $AppID after install are being applied..." "DarkYellow" & "$ModsInstalled" } - #Add mods if deployed from Winget-Install - if (Test-Path ".\mods\$AppID-*") { - #Check if WAU default install path exists - $Mods = "$WAUModsLocation" - if (Test-Path $Mods) { - #Add mods - Write-ToLog "-> Add modifications for $AppID to WAU 'mods'" - Copy-Item ".\mods\$AppID-*" -Destination "$Mods" -Exclude "*installed-once*", "*uninstall*" -Force - } - } - #Add to WAU White List if set if ($WAUWhiteList) { Add-WAUWhiteList $AppID @@ -220,18 +200,22 @@ function Uninstall-App ($AppID, $AppArgs) { #If PreUninstall script exist if ($ModsPreUninstall) { - Write-ToLog "-> Modifications for $AppID before uninstall are being applied..." "Yellow" - & "$ModsPreUninstall" + Write-ToLog "Modifications for $AppID before uninstall are being applied..." "DarkYellow" + $preUnInstallResult = & "$ModsPreUnInstall" + if ($preUnInstallResult -eq $false) { + Write-ToLog "PreUnInstall script for $AppID requested to skip this uninstallation" "Yellow" + return # Exit the function early + } } #Uninstall App - Write-ToLog "-> Uninstalling $AppID..." "Yellow" + Write-ToLog "-> Uninstalling $AppID..." "DarkYellow" $WingetArgs = "uninstall --id $AppID -e --accept-source-agreements -h $AppArgs" -split " " Write-ToLog "-> Running: `"$Winget`" $WingetArgs" - & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append + & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append if ($ModsUninstall) { - Write-ToLog "-> Modifications for $AppID during uninstall are being applied..." "Yellow" + Write-ToLog "-> Modifications for $AppID during uninstall are being applied..." "DarkYellow" & "$ModsUninstall" } @@ -240,21 +224,10 @@ function Uninstall-App ($AppID, $AppArgs) { if (!($IsInstalled)) { Write-ToLog "-> $AppID successfully uninstalled." "Green" if ($ModsUninstalled) { - Write-ToLog "-> Modifications for $AppID after uninstall are being applied..." "Yellow" + Write-ToLog "-> Modifications for $AppID after uninstall are being applied..." "DarkYellow" & "$ModsUninstalled" } - #Remove mods if deployed from Winget-Install - if (Test-Path ".\mods\$AppID-*") { - #Check if WAU default install path exists - $Mods = "$WAUModsLocation" - if (Test-Path "$Mods\$AppID*") { - Write-ToLog "-> Remove $AppID modifications from WAU 'mods'" - #Remove mods - Remove-Item -Path "$Mods\$AppID-*" -Exclude "*uninstall*" -Force - } - } - #Remove from WAU White List if set if ($WAUWhiteList) { Remove-WAUWhiteList $AppID @@ -321,7 +294,8 @@ $Script:IsElevated = $CurrentPrincipal.IsInRole([Security.Principal.WindowsBuilt #Get WAU Installed location $WAURegKey = "HKLM:\SOFTWARE\Romanitho\Winget-AutoUpdate\" $Script:WAUInstallLocation = Get-ItemProperty $WAURegKey -ErrorAction SilentlyContinue | Select-Object -ExpandProperty InstallLocation -$Script:WAUModsLocation = Join-Path -Path $WAUInstallLocation -ChildPath "mods" +# Use the Working Dir (even if it is from a symlink) +$Mods = "$realPath\mods" #Log file & LogPath initialization if ($IsElevated) { diff --git a/Sources/Winget-AutoUpdate/config/winget-detect.ps1 b/Sources/Winget-AutoUpdate/config/winget-detect.ps1 new file mode 100644 index 000000000..ed4d0ade4 --- /dev/null +++ b/Sources/Winget-AutoUpdate/config/winget-detect.ps1 @@ -0,0 +1,53 @@ +#Change app to detect [Application ID] +$AppToDetect = "Notepad++.Notepad++" + + +<# FUNCTIONS #> + +Function Get-WingetCmd { + + $WingetCmd = $null + + #Get WinGet Path + try { + #Get Admin Context Winget Location + $WingetInfo = (Get-Item "$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller_*_8wekyb3d8bbwe\winget.exe").VersionInfo | Sort-Object -Property FileVersionRaw + #If multiple versions, pick most recent one + $WingetCmd = $WingetInfo[-1].FileName + } + catch { + #Get User context Winget Location + if (Test-Path "$env:LocalAppData\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe") { + $WingetCmd = "$env:LocalAppData\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" + } + } + + return $WingetCmd +} + +<# MAIN #> + +#Get WinGet Location Function +$winget = Get-WingetCmd + +#Set json export file +$JsonFile = "$env:TEMP\InstalledApps.json" + +#Get installed apps and version in json file +& $Winget export -o $JsonFile --accept-source-agreements | Out-Null + +#Get json content +$Json = Get-Content $JsonFile -Raw | ConvertFrom-Json + +#Get apps and version in hashtable +$Packages = $Json.Sources.Packages + +#Remove json file +Remove-Item $JsonFile -Force + +# Search for specific app and version +$Apps = $Packages | Where-Object { $_.PackageIdentifier -eq $AppToDetect } + +if ($Apps) { + return "Installed!" +} \ No newline at end of file diff --git a/Sources/Winget-AutoUpdate/functions/Update-App.ps1 b/Sources/Winget-AutoUpdate/functions/Update-App.ps1 index ff174ccf6..d3e48580c 100644 --- a/Sources/Winget-AutoUpdate/functions/Update-App.ps1 +++ b/Sources/Winget-AutoUpdate/functions/Update-App.ps1 @@ -24,26 +24,56 @@ Function Update-App ($app) { #If PreInstall script exist if ($ModsPreInstall) { - Write-ToLog "Modifications for $($app.Id) before upgrade are being applied..." "Yellow" - & "$ModsPreInstall" + Write-ToLog "Modifications for $($app.Id) before upgrade are being applied..." "DarkYellow" + $preInstallResult = & "$ModsPreInstall" + if ($preInstallResult -eq $false) { + Write-ToLog "PreInstall script for $($app.Id) requested to skip this update" "Yellow" + continue # Skip to next app in the parent loop + } } - #Run Winget Upgrade command - if ($ModsOverride) { - Write-ToLog "-> Running (overriding default): Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget --override $ModsOverride" - & $Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget --override $ModsOverride | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append - } - elseif ($ModsCustom) { - Write-ToLog "-> Running (customizing default): Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h --custom $ModsCustom" - & $Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h --custom $ModsCustom | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append - } - else { - Write-ToLog "-> Running: Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h" - & $Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append - } + # Define upgrade base parameters + $baseParams = @( + "upgrade", + "--id", "$($app.Id)", + "-e", + "--accept-package-agreements", + "--accept-source-agreements", + "-s", "winget" + ) + + # Define base log message + $baseLogMessage = "Winget upgrade --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget" + + # Determine which parameters and log message to use + if ($ModsOverride) { + $allParams = $baseParams + @("--override", "$ModsOverride") + $logPrefix = "Running (overriding default):" + $logSuffix = "--override $ModsOverride" + } + elseif ($ModsCustom) { + $allParams = $baseParams + @("-h", "--custom", "$ModsCustom") + $logPrefix = "Running (customizing default):" + $logSuffix = "-h --custom $ModsCustom" + } + else { + $allParams = $baseParams + @("-h") + $logPrefix = "Running:" + $logSuffix = "-h" + } + + # Build the log message + $logMessage = "$logPrefix $baseLogMessage $logSuffix" + + # Log the command + Write-ToLog "-> $logMessage" + + # Execute command and log results + & $Winget $allParams | Where-Object { $_ -notlike " *" } | + Tee-Object -file $LogFile -Append if ($ModsUpgrade) { - Write-ToLog "Modifications for $($app.Id) during upgrade are being applied..." "Yellow" + Write-ToLog "Modifications for $($app.Id) during upgrade are being applied..." "DarkYellow" & "$ModsUpgrade" } @@ -55,31 +85,61 @@ Function Update-App ($app) { #Test for a Pending Reboot (Component Based Servicing/WindowsUpdate/CCM_ClientUtilities) $PendingReboot = Test-PendingReboot if ($PendingReboot -eq $true) { - Write-ToLog "-> A Pending Reboot lingers and probably prohibited $($app.Name) from upgrading...`n-> ...an install for $($app.Name) is NOT executed!" "Red" - continue + Write-ToLog "-> A Pending Reboot lingers and probably prohibited $($app.Name) from upgrading...`n-> ...limiting to 1 install attempt instead of 2" "Yellow" + $retry = 2 } - - #If app failed to upgrade, run Install command (2 tries max - some apps get uninstalled after single "Install" command.) - $retry = 1 + else { + #If app failed to upgrade, run Install command (2 tries max - some apps get uninstalled after single "Install" command.) + $retry = 1 + } + While (($ConfirmInstall -eq $false) -and ($retry -le 2)) { - Write-ToLog "-> An upgrade for $($app.Name) failed, now trying an install instead... ($retry/2)" "Yellow" + Write-ToLog "-> An upgrade for $($app.Name) failed, now trying an install instead... ($retry/2)" "DarkYellow" + # Define install base parameters + $baseParams = @( + "install", + "--id", "$($app.Id)", + "-e", + "--accept-package-agreements", + "--accept-source-agreements", + "-s", "winget", + "--force" + ) + + # Define base log message + $baseLogMessage = "Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget --force" + + # Determine which parameters and log message to use if ($ModsOverride) { - Write-ToLog "-> Running (overriding default): Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget --force --override $ModsOverride" - & $Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget --force --override $ModsOverride | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append - } + $allParams = $baseParams + @("--override", "$ModsOverride") + $logPrefix = "Running (overriding default):" + $logSuffix = "--override $ModsOverride" + } elseif ($ModsCustom) { - Write-ToLog "-> Running (customizing default): Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h --force --custom $ModsCustom" - & $Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h --force --custom $ModsCustom | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append - } + $allParams = $baseParams + @("-h", "--custom", "$ModsCustom") + $logPrefix = "Running (customizing default):" + $logSuffix = "-h --custom $ModsCustom" + } else { - Write-ToLog "-> Running: Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h --force" - & $Winget install --id $($app.Id) -e --accept-package-agreements --accept-source-agreements -s winget -h --force | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append + $allParams = $baseParams + @("-h") + $logPrefix = "Running:" + $logSuffix = "-h" } + # Build the log message + $logMessage = "$logPrefix $baseLogMessage $logSuffix" + + # Log the command + Write-ToLog "-> $logMessage" + + # Execute command and log results + & $Winget $allParams | Where-Object { $_ -notlike " *" } | + Tee-Object -file $LogFile -Append + if ($ModsInstall) { - Write-ToLog "Modifications for $($app.Id) during install are being applied..." "Yellow" + Write-ToLog "Modifications for $($app.Id) during install are being applied..." "DarkYellow" & "$ModsInstall" } @@ -93,14 +153,14 @@ Function Update-App ($app) { # Upgrade/install was successful $true { if ($ModsInstalled) { - Write-ToLog "Modifications for $($app.Id) after upgrade/install are being applied..." "Yellow" + Write-ToLog "Modifications for $($app.Id) after upgrade/install are being applied..." "DarkYellow" & "$ModsInstalled" } } # Upgrade/install was unsuccessful $false { if ($ModsNotInstalled) { - Write-ToLog "Modifications for $($app.Id) after a failed upgrade/install are being applied..." "Yellow" + Write-ToLog "Modifications for $($app.Id) after a failed upgrade/install are being applied..." "DarkYellow" & "$ModsNotInstalled" } } diff --git a/Sources/Winget-AutoUpdate/mods/README.md b/Sources/Winget-AutoUpdate/mods/README.md index e58b8ddfe..f5d83f5ce 100644 --- a/Sources/Winget-AutoUpdate/mods/README.md +++ b/Sources/Winget-AutoUpdate/mods/README.md @@ -24,13 +24,15 @@ The **-install** mod will be used for upgrades too if **-upgrade** doesn't exist `AppID-install.ps1` is recommended because it's used in **both** scenarios. +If **AppID**`-preinstall.ps1`/`-preuninstall.ps1` returns `$false`, the install/update/uninstall for that **AppID** is skipped (checking if an App is running, etc...). + A script **Template** for an all-purpose mod (`_WAU-notinstalled-template.ps1`) is included in which actions can be taken if an upgrade/install fails for any **AppID** (any individual `AppID-notinstalled.ps1` overrides this global one) Name it `_WAU-notinstalled.ps1` for activation ### Winget native parameters: Another finess is the **AppID** followed by the `-override` or `-custom` suffix as a **text file** (**.txt**). > Example: -> **Canneverbe.CDBurnerXP-override.txt** with the content `ADDLOCAL=All REMOVE=Desktop_Shortcut /qn` +> **Adobe.Acrobat.Reader.64-bit-override.txt** with the content `"-sfx_nu /sAll /rs /msi EULA_ACCEPT=YES DISABLEDESKTOPSHORTCUT=1"` > Example: > **ShareX.ShareX-custom.txt** with the content `/MERGETASKS=!CreateDesktopIcon` diff --git a/Sources/Winget-AutoUpdate/mods/_AppID-template.ps1 b/Sources/Winget-AutoUpdate/mods/_AppID-template.ps1 index 265f87c45..4aec7f8d3 100644 --- a/Sources/Winget-AutoUpdate/mods/_AppID-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_AppID-template.ps1 @@ -20,6 +20,10 @@ $RunSystem = "" $RunSwitch = "" $RunWait = $True +# Beginning of Process Name to check for if running - optional wildcard (*) after, without .exe, multiple: "proc1*","proc2" +# If found, it will return $False (to $preInstall-/UninstallResult), stop this script and request for the main script to skip the app. +$SkipApp = @("") + # Beginning of Process Name to Stop - optional wildcard (*) after, without .exe, multiple: "proc1*","proc2" $Proc = @("") @@ -100,6 +104,10 @@ $User = $True if ($RunSystem) { Invoke-ModsApp $RunSystem $RunSwitch $RunWait "" } +if ($SkipApp) { + $result = Skip-ModsProc $SkipApp + if ($result -eq $true) { return $false } +} if ($Proc) { Stop-ModsProc $Proc } diff --git a/Sources/Winget-AutoUpdate/mods/_Mods-Functions.ps1 b/Sources/Winget-AutoUpdate/mods/_Mods-Functions.ps1 index 430b27752..e532dc931 100644 --- a/Sources/Winget-AutoUpdate/mods/_Mods-Functions.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_Mods-Functions.ps1 @@ -18,6 +18,15 @@ function Invoke-ModsApp ($Run, $RunSwitch, $RunWait, $User) { Return } +function Skip-ModsProc ($SkipApp) { + foreach ($process in $SkipApp) { + $running = Get-Process -Name $process -ErrorAction SilentlyContinue + if ($running) { + Return $true + } + } + Return +} function Stop-ModsProc ($Proc) { foreach ($process in $Proc) { @@ -25,6 +34,7 @@ function Stop-ModsProc ($Proc) { } Return } + function Stop-ModsSvc ($Svc) { foreach ($service in $Svc) { Stop-Service -Name $service -Force -ErrorAction SilentlyContinue | Out-Null From 1fe865c2bcb6f3022249358e1f65c5e6e19a52c8 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sat, 21 Jun 2025 17:55:23 +0200 Subject: [PATCH 09/49] Forgotten Tab --- Sources/Winget-AutoUpdate/Winget-Install.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Install.ps1 b/Sources/Winget-AutoUpdate/Winget-Install.ps1 index 985870507..746fb565f 100644 --- a/Sources/Winget-AutoUpdate/Winget-Install.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Install.ps1 @@ -160,7 +160,7 @@ function Install-App ($AppID, $AppArgs) { } Write-ToLog "-> Running: `"$Winget`" $WingetArgs" - & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append + & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append if ($ModsInstall) { Write-ToLog "-> Modifications for $AppID during install are being applied..." "DarkYellow" @@ -212,7 +212,7 @@ function Uninstall-App ($AppID, $AppArgs) { Write-ToLog "-> Uninstalling $AppID..." "DarkYellow" $WingetArgs = "uninstall --id $AppID -e --accept-source-agreements -h $AppArgs" -split " " Write-ToLog "-> Running: `"$Winget`" $WingetArgs" - & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append + & "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append if ($ModsUninstall) { Write-ToLog "-> Modifications for $AppID during uninstall are being applied..." "DarkYellow" @@ -294,6 +294,7 @@ $Script:IsElevated = $CurrentPrincipal.IsInRole([Security.Principal.WindowsBuilt #Get WAU Installed location $WAURegKey = "HKLM:\SOFTWARE\Romanitho\Winget-AutoUpdate\" $Script:WAUInstallLocation = Get-ItemProperty $WAURegKey -ErrorAction SilentlyContinue | Select-Object -ExpandProperty InstallLocation + # Use the Working Dir (even if it is from a symlink) $Mods = "$realPath\mods" From 73f2048fd2420635e47086cc2517b97db05e609f Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 22 Jun 2025 12:20:17 +0200 Subject: [PATCH 10/49] Enhance 'Mods for WAU' with JSON output handling --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 71 +++++++-- .../mods/_WAU-mods-template.ps1 | 147 +++++++++++++++++- 2 files changed, 200 insertions(+), 18 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index ad344eb60..3d7be47b3 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -210,7 +210,7 @@ if (Test-Network) { #Compare if ((Compare-SemVer -Version1 $WAUCurrentVersion -Version2 $WAUAvailableVersion) -lt 0) { #If new version is available, update it - Write-ToLog "WAU Available version: $WAUAvailableVersion" "Yellow"; + Write-ToLog "WAU Available version: $WAUAvailableVersion" "Darkyellow"; Update-WAU; } else { @@ -246,10 +246,10 @@ if (Test-Network) { } if ($NewList) { if ($AlwaysDownloaded) { - Write-ToLog "List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "Yellow" + Write-ToLog "List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "Darkyellow" } else { - Write-ToLog "Newer List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "Yellow" + Write-ToLog "Newer List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "Darkyellow" } $Script:AlwaysDownloaded = $False } @@ -284,14 +284,14 @@ if (Test-Network) { $Script:ReachNoPath = $False } if ($NewMods -gt 0) { - Write-ToLog "$NewMods newer Mods downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))\mods" "Yellow" + Write-ToLog "$NewMods newer Mods downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))\mods" "Darkyellow" } else { if (Test-Path "$WorkingDir\mods\*.ps1") { Write-ToLog "Mods are up to date." "Green" } else { - Write-ToLog "No Mods are implemented..." "Yellow" + Write-ToLog "No Mods are implemented..." "Darkyellow" } } if ($DeletedMods -gt 0) { @@ -302,15 +302,64 @@ if (Test-Network) { #Test if _WAU-mods.ps1 exist: Mods for WAU (if Network is active/any Winget is installed/running as SYSTEM) $Mods = "$WorkingDir\mods" if (Test-Path "$Mods\_WAU-mods.ps1") { - Write-ToLog "Running Mods for WAU..." "Yellow" - & "$Mods\_WAU-mods.ps1" + Write-ToLog "Running Mods for WAU..." "Cyan" + + # Capture both output and exit code + $ModsOutput = & "$Mods\_WAU-mods.ps1" 2>&1 | Out-String $ModsExitCode = $LASTEXITCODE - #If _WAU-mods.ps1 has ExitCode 1 - Re-run WAU + + # Handle legacy exit code behavior first (backward compatibility) if ($ModsExitCode -eq 1) { - Write-ToLog "Re-run WAU" + Write-ToLog "Legacy exit code 1 detected - Re-running WAU" Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" Exit } + + # Try to parse JSON output for new action-based system + if ($ModsOutput -and $ModsOutput.Trim()) { + try { + # Remove any non-JSON content (like debug output) and find JSON + $jsonMatch = $ModsOutput | Select-String -Pattern '\{.*\}' | Select-Object -First 1 + + if ($jsonMatch) { + $ModsResult = $jsonMatch.Matches[0].Value | ConvertFrom-Json + + # Log message if provided + if ($ModsResult.Message) { + $logLevel = if ($ModsResult.LogLevel) { $ModsResult.LogLevel } else { "White" } + Write-ToLog $ModsResult.Message $logLevel + } + + # Execute action based on returned instruction + switch ($ModsResult.Action) { + "Rerun" { + Write-ToLog "Mods requested WAU re-run" + Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" + Exit + } + "Abort" { + Write-ToLog "Mods requested WAU abort" + Exit + } + "Reboot" { + Write-ToLog "Mods requested system reboot" + Restart-Computer -Force + } + "Continue" { + Write-ToLog "Mods allows WAU to continue normally" + # Continue with normal WAU execution + } + default { + Write-ToLog "Unknown action '$($ModsResult.Action)' from mods, continuing normally" "Cyan" + } + } + } + } + catch { + Write-ToLog "Failed to parse mods JSON output: $($_.Exception.Message)" "Red" + Write-ToLog "Continuing with normal WAU execution" "Cyan" + } + } } } @@ -351,7 +400,7 @@ if (Test-Network) { } #Get outdated Winget packages - Write-ToLog "Checking application updates on Winget Repository named '$($Script:WingetSourceCustom)' .." "yellow" + Write-ToLog "Checking application updates on Winget Repository named '$($Script:WingetSourceCustom)' .." "Darkyellow" $outdated = Get-WingetOutdatedApps -src $Script:WingetSourceCustom; #If something unusual happened or no update found @@ -436,7 +485,7 @@ if (Test-Network) { #Test if _WAU-mods-postsys.ps1 exists: Mods for WAU (postsys) - if Network is active/any Winget is installed/running as SYSTEM _after_ SYSTEM updates if ($true -eq $IsSystem) { if (Test-Path "$Mods\_WAU-mods-postsys.ps1") { - Write-ToLog "Running Mods (postsys) for WAU..." "Yellow" + Write-ToLog "Running Mods (postsys) for WAU..." "Darkyellow" & "$Mods\_WAU-mods-postsys.ps1" } } diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index c85e67070..e3933a3e4 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -1,11 +1,82 @@ -<# Mods for WAU (if Network is active/any Winget is installed/running as SYSTEM) -Winget-Upgrade.ps1 calls this script with the code: -[Write-ToLog "Running Mods for WAU..." "Yellow" -& "$Mods\_WAU-mods.ps1"] -Make sure your Functions have unique names! -Exit 1 to Re-run WAU from this script (beware of loops)! +<# +.SYNOPSIS + Custom modifications for Winget-AutoUpdate (WAU) + Runs if Network is active/any Winget is installed/running as SYSTEM + + If mods\_WAU-mods.ps1 exist: Winget-Upgrade.ps1 calls this script with the code: + [Write-ToLog "Running Mods for WAU..." "Cyan" + + # Capture both output and exit code + $ModsOutput = & "$Mods\_WAU-mods.ps1" 2>&1 | Out-String + $ModsExitCode = $LASTEXITCODE] + +.DESCRIPTION + This script runs before the main WAU process and can control WAU execution + by returning a JSON object with action instructions. + + The script should output a JSON object with the following structure: + { + "Action": "string", // Required: Action for WAU to perform + "Message": "string", // Optional: Message to write to WAU log + "LogLevel": "string", // Optional: Log level for the message + "ExitCode": number // Optional: Windows installer exit code for reference + } + + Available Actions: + - "Continue" : Continue with normal WAU execution (default behavior) + - "Abort" : Abort WAU execution completely + - "Rerun" : Re-run WAU (equivalent to legacy exit code 1) + - "Reboot" : Restart the system immediately + + Available LogLevels: + - "White" : Default/normal message + - "Green" : Success message + - "Yellow" : Warning message + - "Red" : Error message + - "Cyan" : Information message + - "Magenta" : Debug message + + Standard Windows Installer Exit Codes (for reference): + - 0 : Success + - 1602 : User cancelled installation + - 1618 : Another installation is in progress + - 3010 : Restart required + - 1641 : Restart initiated by installer + + Examples: + + # Example 1: Abort on specific day + $result = @{ + Action = "Abort" + Message = "WAU disabled on maintenance day" + LogLevel = "Yellow" + ExitCode = 1602 + } | ConvertTo-Json -Compress + + # Example 2: Continue normally + $result = @{ + Action = "Continue" + Message = "All checks passed, proceeding with updates" + LogLevel = "Green" + } | ConvertTo-Json -Compress + + # Example 3: Request reboot after checks + $result = @{ + Action = "Reboot" + Message = "System requires restart before updates" + LogLevel = "Red" + ExitCode = 3010 + } | ConvertTo-Json -Compress + +.NOTES + - This script must always exit with code 0 when using JSON output + - Legacy exit code 1 is still supported for backward compatibility + - Only the first valid JSON object in output will be processed + - If JSON parsing fails, WAU will continue normally + - Make sure your Functions have unique names to avoid conflicts #> + <# FUNCTIONS #> . $PSScriptRoot\_Mods-Functions.ps1 @@ -14,7 +85,69 @@ Exit 1 to Re-run WAU from this script (beware of loops)! <# MAIN #> +# Add your custom logic here + +<# +# Example implementation: Second Tuesday of month check +$today = Get-Date +$firstDayOfMonth = [DateTime]::new($today.Year, $today.Month, 1) +$firstTuesday = $firstDayOfMonth.AddDays((2 - [int]$firstDayOfMonth.DayOfWeek + 7) % 7) +$secondTuesday = $firstTuesday.AddDays(7) + +if ($today.Date -ne $secondTuesday.Date) { + # Not second Tuesday - abort WAU execution + $result = @{ + Action = "Abort" + Message = "Today is not the second Tuesday of the month. WAU execution aborted." + LogLevel = "Yellow" + ExitCode = 1602 # User cancelled + } | ConvertTo-Json -Compress + + Write-Output $result + Exit 0 +} + +# Example: Check if maintenance window is active +$maintenanceStart = Get-Date "02:00" +$maintenanceEnd = Get-Date "04:00" +$currentTime = Get-Date + +if ($currentTime -ge $maintenanceStart -and $currentTime -le $maintenanceEnd) { + $result = @{ + Action = "Abort" + Message = "WAU aborted during maintenance window ($($maintenanceStart.ToString('HH:mm')) - $($maintenanceEnd.ToString('HH:mm')))" + LogLevel = "Yellow" + ExitCode = 1602 + } | ConvertTo-Json -Compress + + Write-Output $result + Exit 0 +} + +# Example: Check available disk space +$systemDrive = Get-PSDrive -Name ($env:SystemDrive.TrimEnd(':')) +$freeSpaceGB = [math]::Round($systemDrive.Free / 1GB, 2) +$minimumSpaceGB = 5 + +if ($freeSpaceGB -lt $minimumSpaceGB) { + $result = @{ + Action = "Abort" + Message = "Insufficient disk space: ${freeSpaceGB}GB available, ${minimumSpaceGB}GB required" + LogLevel = "Red" + ExitCode = 1618 # Another installation is in progress (or system busy) + } | ConvertTo-Json -Compress + + Write-Output $result + Exit 0 +} +# All checks passed - continue with normal WAU execution +$result = @{ + Action = "Continue" + Message = "Second Tuesday check passed. No maintenance window. Sufficient disk space (${freeSpaceGB}GB). Continuing with WAU execution." + LogLevel = "Green" +} | ConvertTo-Json -Compress -Write-ToLog "...nothing to do!" "Green" +Write-Output $result Exit 0 +#> \ No newline at end of file From 58dfe8fa84559d8c60e99cf9e3ba032b122e508f Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 22 Jun 2025 13:33:11 +0200 Subject: [PATCH 11/49] Beware! --- Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index e3933a3e4..7c29d33ab 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -74,6 +74,7 @@ - Only the first valid JSON object in output will be processed - If JSON parsing fails, WAU will continue normally - Make sure your Functions have unique names to avoid conflicts + - Beware of logic loops or long-running operations that may loop indefinitely or block WAU execution! #> From b339543f9017899938ffe2a048b8163242940605 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 22 Jun 2025 14:17:44 +0200 Subject: [PATCH 12/49] Exit for Reboot too... --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 3d7be47b3..3f4a603ea 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -344,6 +344,7 @@ if (Test-Network) { "Reboot" { Write-ToLog "Mods requested system reboot" Restart-Computer -Force + Exit } "Continue" { Write-ToLog "Mods allows WAU to continue normally" From f89d080cbb00e5af5182ea503b66e70363003c80 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 22 Jun 2025 21:10:45 +0200 Subject: [PATCH 13/49] WAU Exit Codes from Mods too or standard --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 3f4a603ea..349b268ce 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -335,20 +335,23 @@ if (Test-Network) { "Rerun" { Write-ToLog "Mods requested WAU re-run" Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" - Exit + $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 0 } + Exit $exitCode } "Abort" { Write-ToLog "Mods requested WAU abort" - Exit + $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" + Exit $exitCode } "Reboot" { Write-ToLog "Mods requested system reboot" Restart-Computer -Force - Exit + $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" + Exit $exitCode } "Continue" { Write-ToLog "Mods allows WAU to continue normally" - # Continue with normal WAU execution + # Continue with normal WAU execution - no exit needed } default { Write-ToLog "Unknown action '$($ModsResult.Action)' from mods, continuing normally" "Cyan" From c343fec3121ae9c7887140fcadadfa18291a503b Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 22 Jun 2025 23:05:30 +0200 Subject: [PATCH 14/49] Safe Reboot with delay notifying users --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 34 ++++++++++++++----- .../mods/_WAU-mods-template.ps1 | 6 ++-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 349b268ce..ea91c2dff 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -332,28 +332,44 @@ if (Test-Network) { # Execute action based on returned instruction switch ($ModsResult.Action) { - "Rerun" { - Write-ToLog "Mods requested WAU re-run" + "Rerun" { + Write-ToLog "Mods requested a WAU re-run" Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 0 } Exit $exitCode } - "Abort" { - Write-ToLog "Mods requested WAU abort" + "Abort" { + Write-ToLog "Mods requested WAU to abort" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" Exit $exitCode } - "Reboot" { - Write-ToLog "Mods requested system reboot" - Restart-Computer -Force + "Reboot" { + Write-ToLog "Mods requested a system reboot" + + # Get configurable delay, default to 300 seconds (5 minutes) + $rebootDelay = if ($ModsResult.RebootDelay) { + $ModsResult.RebootDelay + } else { + 300 + } + + # Ensure minimum delay of 60 seconds for safety + if ($rebootDelay -lt 60) { + $rebootDelay = 60 + Write-ToLog "Reboot delay adjusted to minimum 60 seconds" "Yellow" + } + + $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot" } + & shutdown /r /t $rebootDelay /c $shutdownMessage + Write-ToLog "System restart scheduled in $rebootDelay seconds" "Yellow" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" Exit $exitCode } - "Continue" { + "Continue" { Write-ToLog "Mods allows WAU to continue normally" # Continue with normal WAU execution - no exit needed } - default { + default { Write-ToLog "Unknown action '$($ModsResult.Action)' from mods, continuing normally" "Cyan" } } diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index 7c29d33ab..aea659117 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -19,7 +19,8 @@ "Action": "string", // Required: Action for WAU to perform "Message": "string", // Optional: Message to write to WAU log "LogLevel": "string", // Optional: Log level for the message - "ExitCode": number // Optional: Windows installer exit code for reference + "ExitCode": number, // Optional: Windows installer exit code for reference + "RebootDelay": number // Optional: Delay in seconds before rebooting (default 300 seconds (5 minutes)) } Available Actions: @@ -66,6 +67,7 @@ Message = "System requires restart before updates" LogLevel = "Red" ExitCode = 3010 + RebootDelay = 300 # Optional: Delay before rebooting (default is 300 seconds) } | ConvertTo-Json -Compress .NOTES @@ -151,4 +153,4 @@ $result = @{ Write-Output $result Exit 0 -#> \ No newline at end of file +#> From 8f26ac3b0f74b0f186094ea38a608e0de387f0b0 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Mon, 23 Jun 2025 11:04:07 +0200 Subject: [PATCH 15/49] Small text edits --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 4 ++-- Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index ea91c2dff..0c359e4c1 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -358,8 +358,8 @@ if (Test-Network) { $rebootDelay = 60 Write-ToLog "Reboot delay adjusted to minimum 60 seconds" "Yellow" } - - $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot" } + + $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay seconds" } & shutdown /r /t $rebootDelay /c $shutdownMessage Write-ToLog "System restart scheduled in $rebootDelay seconds" "Yellow" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index aea659117..674c9f27c 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -27,7 +27,7 @@ - "Continue" : Continue with normal WAU execution (default behavior) - "Abort" : Abort WAU execution completely - "Rerun" : Re-run WAU (equivalent to legacy exit code 1) - - "Reboot" : Restart the system immediately + - "Reboot" : Restart the system with delay and notification to end user Available LogLevels: - "White" : Default/normal message @@ -64,7 +64,7 @@ # Example 3: Request reboot after checks $result = @{ Action = "Reboot" - Message = "System requires restart before updates" + Message = "The system needs to reboot within 5 minutes`nbefore WAU updates can be performed." LogLevel = "Red" ExitCode = 3010 RebootDelay = 300 # Optional: Delay before rebooting (default is 300 seconds) From 4fc97e5837c266f1f828f89cf6a56c627e922d25 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Mon, 23 Jun 2025 11:27:18 +0200 Subject: [PATCH 16/49] Default reboot message in minutes instead of seconds --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 0c359e4c1..8ed976ed7 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -359,7 +359,7 @@ if (Test-Network) { Write-ToLog "Reboot delay adjusted to minimum 60 seconds" "Yellow" } - $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay seconds" } + $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $($rebootDelay / 60) minutes" } & shutdown /r /t $rebootDelay /c $shutdownMessage Write-ToLog "System restart scheduled in $rebootDelay seconds" "Yellow" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" From f939719dc1d59075a9ea6f8d2890c5a574322b28 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Tue, 24 Jun 2025 17:33:49 +0200 Subject: [PATCH 17/49] 'Postpone' as an Action too --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 43 +++++++++++---- .../mods/_WAU-mods-template.ps1 | 53 ++++++++++++++++--- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 8ed976ed7..c11a66a35 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -343,31 +343,52 @@ if (Test-Network) { $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" Exit $exitCode } + "Postpone" { + Write-ToLog "Mods requested a postpone of WAU" + $postponeDuration = if ($ModsResult.PostponeDuration) { + $ModsResult.PostponeDuration + } else { + 1 + } + # Create a postponed temporary scheduled task to try again later + $uniqueTaskName = "Postponed-$($Script:GitHub_Repo)_$(Get-Random)" + $taskPath = "\WAU\" + $copyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($WAUConfig.InstallLocation)winget-upgrade.ps1`"" + $copyTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours($postponeDuration) + # Set EndBoundary to make DeleteExpiredTaskAfter work + $copyTrigger.EndBoundary = (Get-Date).AddHours($postponeDuration).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") + $copySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) + $copyPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest + Register-ScheduledTask -TaskName $uniqueTaskName -TaskPath $taskPath -Action $copyAction -Trigger $copyTrigger -Settings $copySettings -Principal $copyPrincipal -Description "Postponed copy of $Script:GitHub_Repo" | Out-Null + + Write-ToLog "WAU will try again in $postponeDuration hours" "Yellow" + $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" + Exit $exitCode + } "Reboot" { Write-ToLog "Mods requested a system reboot" - # Get configurable delay, default to 300 seconds (5 minutes) + # Get configurable delay, default to 5 minutes $rebootDelay = if ($ModsResult.RebootDelay) { $ModsResult.RebootDelay } else { - 300 + 5 } - - # Ensure minimum delay of 60 seconds for safety - if ($rebootDelay -lt 60) { - $rebootDelay = 60 - Write-ToLog "Reboot delay adjusted to minimum 60 seconds" "Yellow" + + # Ensure minimum delay of 1 minute for safety + if ($rebootDelay -lt 1) { + $rebootDelay = 1 + Write-ToLog "Reboot delay adjusted to minimum 1 minute" "Yellow" } - $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $($rebootDelay / 60) minutes" } - & shutdown /r /t $rebootDelay /c $shutdownMessage - Write-ToLog "System restart scheduled in $rebootDelay seconds" "Yellow" + $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay minutes" } + & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage + Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" Exit $exitCode } "Continue" { Write-ToLog "Mods allows WAU to continue normally" - # Continue with normal WAU execution - no exit needed } default { Write-ToLog "Unknown action '$($ModsResult.Action)' from mods, continuing normally" "Cyan" diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index 674c9f27c..528b47644 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -16,16 +16,18 @@ The script should output a JSON object with the following structure: { - "Action": "string", // Required: Action for WAU to perform - "Message": "string", // Optional: Message to write to WAU log - "LogLevel": "string", // Optional: Log level for the message - "ExitCode": number, // Optional: Windows installer exit code for reference - "RebootDelay": number // Optional: Delay in seconds before rebooting (default 300 seconds (5 minutes)) + "Action": "string", // Required: Action for WAU to perform + "Message": "string", // Optional: Message to write to WAU log + "LogLevel": "string", // Optional: Log level for the message + "ExitCode": number, // Optional: Windows installer exit code for reference + "PostponeDuration": number, // Optional: Postpone in hours before running WAU again (default 1 hour) + "RebootDelay": number // Optional: Delay in minutes before rebooting (default 5 minutes) } Available Actions: - "Continue" : Continue with normal WAU execution (default behavior) - "Abort" : Abort WAU execution completely + - "Postpone" : Delay WAU execution temporarily with 'PostponeDuration' hours - "Rerun" : Re-run WAU (equivalent to legacy exit code 1) - "Reboot" : Restart the system with delay and notification to end user @@ -54,6 +56,14 @@ ExitCode = 1602 } | ConvertTo-Json -Compress + $result = @{ + Action = "Postpone" + Message = "WAU postponed due to maintenance schedule" + LogLevel = "Yellow" + ExitCode = 1602 + PostponeDuration = 2 # Optional: Postpone WAU execution for 2 hours (default is 1 hour) + } | ConvertTo-Json -Compress + # Example 2: Continue normally $result = @{ Action = "Continue" @@ -67,7 +77,7 @@ Message = "The system needs to reboot within 5 minutes`nbefore WAU updates can be performed." LogLevel = "Red" ExitCode = 3010 - RebootDelay = 300 # Optional: Delay before rebooting (default is 300 seconds) + RebootDelay = 5 # Optional: Delay before rebooting (default is 5 minutes) } | ConvertTo-Json -Compress .NOTES @@ -144,10 +154,39 @@ if ($freeSpaceGB -lt $minimumSpaceGB) { Exit 0 } +# Example: Check Windows Update registry keys for installation status +$wuKeys = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install", + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending" +) + +foreach ($key in $wuKeys) { + if (Test-Path $key) { + $lastInstall = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue + if ($lastInstall -and (Get-Date).AddMinutes(-30) -lt [DateTime]$lastInstall.LastSuccessTime) { + $wuInProgress = $true + break + } + } +} + +if ($wuInProgress) { + $result = @{ + Action = "Postpone" + Message = "Windows Update is currently installing. WAU postponed for 2 hours." + LogLevel = "Yellow" + ExitCode = 1618 + PostponeDuration = 2 + } | ConvertTo-Json -Compress + + Write-Output $result + Exit 0 +} + # All checks passed - continue with normal WAU execution $result = @{ Action = "Continue" - Message = "Second Tuesday check passed. No maintenance window. Sufficient disk space (${freeSpaceGB}GB). Continuing with WAU execution." + Message = "Second Tuesday check passed. No maintenance window. Sufficient disk space (${freeSpaceGB}GB). No Windows Update in progress. Continuing with WAU execution." LogLevel = "Green" } | ConvertTo-Json -Compress From 06eea1d36f9cdf885644b12c9f216614a9a90359 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Tue, 24 Jun 2025 18:26:43 +0200 Subject: [PATCH 18/49] Better WU check for Postpone --- .../mods/_WAU-mods-template.ps1 | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index 528b47644..9f8791c09 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -155,6 +155,7 @@ if ($freeSpaceGB -lt $minimumSpaceGB) { } # Example: Check Windows Update registry keys for installation status +$wuInProgress = $false $wuKeys = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending" @@ -163,13 +164,29 @@ $wuKeys = @( foreach ($key in $wuKeys) { if (Test-Path $key) { $lastInstall = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue - if ($lastInstall -and (Get-Date).AddMinutes(-30) -lt [DateTime]$lastInstall.LastSuccessTime) { - $wuInProgress = $true - break + if ($lastInstall -and $lastInstall.PSObject.Properties.Name -contains "LastSuccessTime" -and $lastInstall.LastSuccessTime) { + try { + $lastSuccessTime = [DateTime]$lastInstall.LastSuccessTime + # If the last successful install was within the last 30 minutes, consider WU in progress + if ((Get-Date).AddMinutes(-30) -lt $lastSuccessTime) { + $wuInProgress = $true + break + } + } + catch { + # Failed to parse date, skip this check + continue + } } } } +# Check if Windows Update service is running +$wuInProgress = $wuInProgress -or (Get-Service -Name "wuauserv" -ErrorAction SilentlyContinue).Status -eq "Running" + +# Check for specific Windows Update processes (TiWorker and TrustedInstaller are strong indicators) +$wuInProgress = $wuInProgress -or (Get-Process -Name "TiWorker","TrustedInstaller" -ErrorAction SilentlyContinue).Count -gt 0 + if ($wuInProgress) { $result = @{ Action = "Postpone" From b1131ef3c04cce47eda51e82ccdce500518b6d4e Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Tue, 24 Jun 2025 18:47:04 +0200 Subject: [PATCH 19/49] Small text change --- Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index 9f8791c09..b413249f1 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -56,6 +56,7 @@ ExitCode = 1602 } | ConvertTo-Json -Compress + # Example 2: Postpone WAU execution $result = @{ Action = "Postpone" Message = "WAU postponed due to maintenance schedule" @@ -64,14 +65,14 @@ PostponeDuration = 2 # Optional: Postpone WAU execution for 2 hours (default is 1 hour) } | ConvertTo-Json -Compress - # Example 2: Continue normally + # Example 3: Continue normally $result = @{ Action = "Continue" Message = "All checks passed, proceeding with updates" LogLevel = "Green" } | ConvertTo-Json -Compress - - # Example 3: Request reboot after checks + + # Example 4: Request reboot after checks $result = @{ Action = "Reboot" Message = "The system needs to reboot within 5 minutes`nbefore WAU updates can be performed." From f8bb37175a0181d24ae74ac0fa66cb98f3eaece8 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Wed, 25 Jun 2025 09:13:20 +0200 Subject: [PATCH 20/49] Error handling --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 85 +++++++++++++------ .../mods/_WAU-mods-template.ps1 | 4 +- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index c11a66a35..2f62030c9 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -345,45 +345,76 @@ if (Test-Network) { } "Postpone" { Write-ToLog "Mods requested a postpone of WAU" - $postponeDuration = if ($ModsResult.PostponeDuration) { - $ModsResult.PostponeDuration - } else { - 1 + # Check if a postponed task already exists + $existingTask = Get-ScheduledTask -TaskPath "\WAU\" -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like "Postponed-$($Script:GitHub_Repo)*" } + if ($existingTask) { + Write-ToLog "A postponed task for $($Script:GitHub_Repo) already exists, not creating another." "Yellow" + } + else { + # Get configurable duration, default to 1 hour + $postponeDuration = if ($ModsResult.PostponeDuration) { + try { + [double]$parsedDuration = [double]$ModsResult.PostponeDuration + # Ensure minimum duration of 0.1 hours (6 minutes) + if ($parsedDuration -lt 0.1) { + Write-ToLog "PostponeDuration adjusted to minimum 0.1 hours (6 minutes)" "Yellow" + 0.1 + } else { + $parsedDuration + } + } + catch { + Write-ToLog "Invalid PostponeDuration value '$($ModsResult.PostponeDuration)', using default 1 hour" "Yellow" + 1 + } + } else { + 1 + } + + # Create a postponed temporary scheduled task to try again later + $uniqueTaskName = "Postponed-$($Script:GitHub_Repo)_$(Get-Random)" + $taskPath = "\WAU\" + $copyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($WAUConfig.InstallLocation)Winget-Upgrade.ps1`"" + $copyTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours($postponeDuration) + # Set EndBoundary to make DeleteExpiredTaskAfter work + $copyTrigger.EndBoundary = (Get-Date).AddHours($postponeDuration).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") + $copySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) + $copyPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest + Register-ScheduledTask -TaskName $uniqueTaskName -TaskPath $taskPath -Action $copyAction -Trigger $copyTrigger -Settings $copySettings -Principal $copyPrincipal -Description "Postponed copy of $Script:GitHub_Repo" | Out-Null + Write-ToLog "WAU will try again in $postponeDuration hours" "Yellow" } - # Create a postponed temporary scheduled task to try again later - $uniqueTaskName = "Postponed-$($Script:GitHub_Repo)_$(Get-Random)" - $taskPath = "\WAU\" - $copyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($WAUConfig.InstallLocation)winget-upgrade.ps1`"" - $copyTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours($postponeDuration) - # Set EndBoundary to make DeleteExpiredTaskAfter work - $copyTrigger.EndBoundary = (Get-Date).AddHours($postponeDuration).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") - $copySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) - $copyPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - Register-ScheduledTask -TaskName $uniqueTaskName -TaskPath $taskPath -Action $copyAction -Trigger $copyTrigger -Settings $copySettings -Principal $copyPrincipal -Description "Postponed copy of $Script:GitHub_Repo" | Out-Null - - Write-ToLog "WAU will try again in $postponeDuration hours" "Yellow" $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" Exit $exitCode } "Reboot" { Write-ToLog "Mods requested a system reboot" - # Get configurable delay, default to 5 minutes - $rebootDelay = if ($ModsResult.RebootDelay) { - $ModsResult.RebootDelay + $rebootDelay = if ($ModsResult.RebootDelay) { + try { + [double]$parsedDelay = [double]$ModsResult.RebootDelay + # Ensure minimum delay of 1 minute for safety + if ($parsedDelay -lt 1) { + Write-ToLog "RebootDelay adjusted to minimum 1 minute" "Yellow" + 1 + } else { + $parsedDelay + } + } + catch { + Write-ToLog "Invalid RebootDelay value '$($ModsResult.RebootDelay)', using default 5 minutes" "Yellow" + 5 + } } else { 5 } - # Ensure minimum delay of 1 minute for safety - if ($rebootDelay -lt 1) { - $rebootDelay = 1 - Write-ToLog "Reboot delay adjusted to minimum 1 minute" "Yellow" - } - $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay minutes" } - & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage - Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" + $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 + if ([string]::IsNullOrEmpty($result)) { + Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" + } else { + Write-ToLog "A system shutdown has already been scheduled" "Yellow" + } $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" Exit $exitCode } diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index b413249f1..c06c83a10 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -20,14 +20,14 @@ "Message": "string", // Optional: Message to write to WAU log "LogLevel": "string", // Optional: Log level for the message "ExitCode": number, // Optional: Windows installer exit code for reference - "PostponeDuration": number, // Optional: Postpone in hours before running WAU again (default 1 hour) + "PostponeDuration": number, // Optional: Postpone duration in hours before running WAU again (default 1 hour) "RebootDelay": number // Optional: Delay in minutes before rebooting (default 5 minutes) } Available Actions: - "Continue" : Continue with normal WAU execution (default behavior) - "Abort" : Abort WAU execution completely - - "Postpone" : Delay WAU execution temporarily with 'PostponeDuration' hours + - "Postpone" : Postpone WAU execution temporarily with 'PostponeDuration' hours - "Rerun" : Re-run WAU (equivalent to legacy exit code 1) - "Reboot" : Restart the system with delay and notification to end user From 253dc4dfde7a225aabf90c9421a9f5464c3aa444 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Thu, 26 Jun 2025 08:03:31 +0200 Subject: [PATCH 21/49] Reboot: now basic SCCM client awareness --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 107 ++++++++++++++++-- .../mods/_WAU-mods-template.ps1 | 8 +- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 2f62030c9..f11f20b5e 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -335,12 +335,12 @@ if (Test-Network) { "Rerun" { Write-ToLog "Mods requested a WAU re-run" Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" - $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 0 } + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 0 } Exit $exitCode } "Abort" { Write-ToLog "Mods requested WAU to abort" - $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" Exit $exitCode } "Postpone" { @@ -383,7 +383,7 @@ if (Test-Network) { Register-ScheduledTask -TaskName $uniqueTaskName -TaskPath $taskPath -Action $copyAction -Trigger $copyTrigger -Settings $copySettings -Principal $copyPrincipal -Description "Postponed copy of $Script:GitHub_Repo" | Out-Null Write-ToLog "WAU will try again in $postponeDuration hours" "Yellow" } - $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" Exit $exitCode } "Reboot" { @@ -409,13 +409,104 @@ if (Test-Network) { } $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay minutes" } - $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 - if ([string]::IsNullOrEmpty($result)) { - Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" + + # Check if SCCM client is available for managed restart (user controlled) + $sccmClient = Get-CimInstance -Namespace "root\ccm" -ClassName "SMS_Client" -ErrorAction SilentlyContinue + + if ($sccmClient) { + Write-ToLog "SCCM client detected - using managed restart (user controlled)" "Green" + + try { + $ccmRestartPath = "$env:windir\CCM\CcmRestart.exe" + $regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' + + # Check if SCCM restart registry values already exist + $existingRebootBy = $null + $existingRebootValues = $false + + if (Test-Path $regPath) { + $existingRebootBy = Get-ItemProperty -Path $regPath -Name 'RebootBy' -ErrorAction SilentlyContinue + $existingNotifyUI = Get-ItemProperty -Path $regPath -Name 'NotifyUI' -ErrorAction SilentlyContinue + $existingSetTime = Get-ItemProperty -Path $regPath -Name 'SetTime' -ErrorAction SilentlyContinue + + # Check if we have the key registry values indicating a restart is already scheduled + if ($existingRebootBy -and $existingNotifyUI -and $existingSetTime -and $existingRebootBy.PSObject.Properties['RebootBy']) { + $existingRebootValues = $true + $existingRestartTime = [DateTimeOffset]::FromUnixTimeSeconds([int64]$existingRebootBy.RebootBy).LocalDateTime + Write-ToLog "SCCM restart already scheduled for: $existingRestartTime" "Yellow" + } + } + + if ($existingRebootValues) { + # Try CcmRestart.exe for notification + if (Test-Path $ccmRestartPath) { + Write-ToLog "Triggering SCCM restart notification via CcmRestart.exe" "Cyan" + Start-Process -FilePath $ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + } else { + Write-ToLog "CcmRestart.exe not found, restarting ccmexec service" "Yellow" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + } + } else { + # No existing restart scheduled - create new SCCM managed restart (user controlled) + Write-ToLog "Setting up new SCCM managed restart schedule" "Green" + + # Check the intended exit code to determine restart type + $intendedExitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } + $hardRebootValue = if ($intendedExitCode -eq 1641) { 1 } else { 0 } + + + if ($intendedExitCode -eq 1641) { + Write-ToLog "Exit code 1641 detected - using hard reboot for SCCM restart" "Yellow" + } else { + Write-ToLog "Using soft reboot for SCCM restart (exit code: $intendedExitCode)" "Cyan" + } + + $restartTime = [DateTimeOffset]::Now.AddMinutes($rebootDelay).ToUnixTimeSeconds() + + # Ensure registry path exists + if (-not (Test-Path $regPath)) { + New-Item -Path $regPath -Force | Out-Null + } + + # Set restart properties for SCCM + New-ItemProperty -Path $regPath -Name 'RebootBy' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'HardReboot' -Value $hardRebootValue -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null + + # Try CcmRestart.exe first for notification + if (Test-Path $ccmRestartPath) { + Write-ToLog "Triggering SCCM restart notification via CcmRestart.exe" "Cyan" + Start-Process -FilePath $ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + } else { + Write-ToLog "CcmRestart.exe not found, restarting ccmexec service" "Yellow" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + } + + Write-ToLog "SCCM managed restart scheduled for: $([DateTimeOffset]::FromUnixTimeSeconds($restartTime).LocalDateTime)" "Green" + } + } + catch { + Write-ToLog "Failed to set SCCM restart: $($_.Exception.Message). Falling back to standard restart." "Yellow" + # Fallback to standard shutdown + $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-ToLog "System restart scheduled in $rebootDelay minutes (fallback)" "Yellow" + } else { + Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" + } + } } else { - Write-ToLog "A system shutdown has already been scheduled" "Yellow" + # Standard shutdown when SCCM is not available + $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" + } else { + Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" + } } - $exitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } # Default to "Restart required" + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 3010 } # Default to "Restart required" Exit $exitCode } "Continue" { diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index c06c83a10..b6476d9bb 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -43,8 +43,8 @@ - 0 : Success - 1602 : User cancelled installation - 1618 : Another installation is in progress - - 3010 : Restart required - - 1641 : Restart initiated by installer + - 3010 : Restart required (SCCM Soft Reboot) + - 1641 : Restart initiated by installer (SCCM Hard Reboot) Examples: @@ -75,10 +75,10 @@ # Example 4: Request reboot after checks $result = @{ Action = "Reboot" - Message = "The system needs to reboot within 5 minutes`nbefore WAU updates can be performed." + Message = "The system needs to reboot within 5 minutes before WAU updates can be performed." LogLevel = "Red" ExitCode = 3010 - RebootDelay = 5 # Optional: Delay before rebooting (default is 5 minutes) + RebootDelay = 10 # Optional: Delay before rebooting (default is 5 minutes) } | ConvertTo-Json -Compress .NOTES From 3d691b4e813464347550863c4c5691d82dd85e47 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Thu, 26 Jun 2025 09:54:31 +0200 Subject: [PATCH 22/49] Detail: DarkYellow --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index f11f20b5e..516b2f9d7 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -210,7 +210,7 @@ if (Test-Network) { #Compare if ((Compare-SemVer -Version1 $WAUCurrentVersion -Version2 $WAUAvailableVersion) -lt 0) { #If new version is available, update it - Write-ToLog "WAU Available version: $WAUAvailableVersion" "Darkyellow"; + Write-ToLog "WAU Available version: $WAUAvailableVersion" "DarkYellow"; Update-WAU; } else { @@ -246,10 +246,10 @@ if (Test-Network) { } if ($NewList) { if ($AlwaysDownloaded) { - Write-ToLog "List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "Darkyellow" + Write-ToLog "List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "DarkYellow" } else { - Write-ToLog "Newer List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "Darkyellow" + Write-ToLog "Newer List downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))" "DarkYellow" } $Script:AlwaysDownloaded = $False } @@ -284,14 +284,14 @@ if (Test-Network) { $Script:ReachNoPath = $False } if ($NewMods -gt 0) { - Write-ToLog "$NewMods newer Mods downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))\mods" "Darkyellow" + Write-ToLog "$NewMods newer Mods downloaded/copied to local path: $($WAUConfig.InstallLocation.TrimEnd(" ", "\"))\mods" "DarkYellow" } else { if (Test-Path "$WorkingDir\mods\*.ps1") { Write-ToLog "Mods are up to date." "Green" } else { - Write-ToLog "No Mods are implemented..." "Darkyellow" + Write-ToLog "No Mods are implemented..." "DarkYellow" } } if ($DeletedMods -gt 0) { @@ -563,7 +563,7 @@ if (Test-Network) { } #Get outdated Winget packages - Write-ToLog "Checking application updates on Winget Repository named '$($Script:WingetSourceCustom)' .." "Darkyellow" + Write-ToLog "Checking application updates on Winget Repository named '$($Script:WingetSourceCustom)' .." "DarkYellow" $outdated = Get-WingetOutdatedApps -src $Script:WingetSourceCustom; #If something unusual happened or no update found @@ -648,7 +648,7 @@ if (Test-Network) { #Test if _WAU-mods-postsys.ps1 exists: Mods for WAU (postsys) - if Network is active/any Winget is installed/running as SYSTEM _after_ SYSTEM updates if ($true -eq $IsSystem) { if (Test-Path "$Mods\_WAU-mods-postsys.ps1") { - Write-ToLog "Running Mods (postsys) for WAU..." "Darkyellow" + Write-ToLog "Running Mods (postsys) for WAU..." "DarkYellow" & "$Mods\_WAU-mods-postsys.ps1" } } From 1917b613e4b0ae4d7118f43d6573242696ca3b28 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Thu, 26 Jun 2025 12:56:45 +0200 Subject: [PATCH 23/49] Add reboot handler option in _WAU-mods-template.ps1 and the check --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 5 +++-- Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 516b2f9d7..a256fe08f 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -409,11 +409,12 @@ if (Test-Network) { } $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay minutes" } + $rebootHandler = if ($ModsResult.RebootHandler) { $ModsResult.RebootHandler } else { "Windows" } # Check if SCCM client is available for managed restart (user controlled) $sccmClient = Get-CimInstance -Namespace "root\ccm" -ClassName "SMS_Client" -ErrorAction SilentlyContinue - if ($sccmClient) { + if ($sccmClient -and ($rebootHandler -eq "SCCM")) { Write-ToLog "SCCM client detected - using managed restart (user controlled)" "Green" try { @@ -498,7 +499,7 @@ if (Test-Network) { } } } else { - # Standard shutdown when SCCM is not available + # Standard shutdown when SCCM is not available (or "Windows" expplicitly requested as reboot handler) $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 if ($LASTEXITCODE -eq 0) { Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index b6476d9bb..a09509b7a 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -21,7 +21,8 @@ "LogLevel": "string", // Optional: Log level for the message "ExitCode": number, // Optional: Windows installer exit code for reference "PostponeDuration": number, // Optional: Postpone duration in hours before running WAU again (default 1 hour) - "RebootDelay": number // Optional: Delay in minutes before rebooting (default 5 minutes) + "RebootDelay": number, // Optional: Delay in minutes before rebooting (default 5 minutes) + "RebootHandler": string // Optional: "SCCM" or "Windows" (default "Windows") to specify reboot handler } Available Actions: @@ -75,10 +76,11 @@ # Example 4: Request reboot after checks $result = @{ Action = "Reboot" - Message = "The system needs to reboot within 5 minutes before WAU updates can be performed." + Message = "The system needs to reboot within 15 minutes before WAU updates can be performed." LogLevel = "Red" ExitCode = 3010 - RebootDelay = 10 # Optional: Delay before rebooting (default is 5 minutes) + RebootDelay = 15 # Optional: Delay before rebooting (default is 5 minutes) + RebootHandler = "SCCM" # Optional: Specify reboot handler (default is "Windows") } | ConvertTo-Json -Compress .NOTES From fbb8862e4f2f4efd1b5cd27bd7eb4fded3bbbdfb Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Thu, 26 Jun 2025 13:21:03 +0200 Subject: [PATCH 24/49] Wording more logical --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index a256fe08f..976542698 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -499,7 +499,7 @@ if (Test-Network) { } } } else { - # Standard shutdown when SCCM is not available (or "Windows" expplicitly requested as reboot handler) + # Standard shutdown when SCCM is not available (or reboot handler is not "SCCM") $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 if ($LASTEXITCODE -eq 0) { Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" From c9be72caac025fd3affbff20c707a7d356a2f02e Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Thu, 26 Jun 2025 21:19:46 +0200 Subject: [PATCH 25/49] Full SCCM client awareness (Soft/Hard Reboot) --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 108 ++++++++++++++---- .../mods/_WAU-mods-template.ps1 | 2 +- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 976542698..b6a05e4d0 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -417,10 +417,10 @@ if (Test-Network) { if ($sccmClient -and ($rebootHandler -eq "SCCM")) { Write-ToLog "SCCM client detected - using managed restart (user controlled)" "Green" + $ccmRestartPath = "$env:windir\CCM\CcmRestart.exe" + $regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' + try { - $ccmRestartPath = "$env:windir\CCM\CcmRestart.exe" - $regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' - # Check if SCCM restart registry values already exist $existingRebootBy = $null $existingRebootValues = $false @@ -451,30 +451,88 @@ if (Test-Network) { # No existing restart scheduled - create new SCCM managed restart (user controlled) Write-ToLog "Setting up new SCCM managed restart schedule" "Green" - # Check the intended exit code to determine restart type - $intendedExitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } - $hardRebootValue = if ($intendedExitCode -eq 1641) { 1 } else { 0 } - - - if ($intendedExitCode -eq 1641) { - Write-ToLog "Exit code 1641 detected - using hard reboot for SCCM restart" "Yellow" - } else { - Write-ToLog "Using soft reboot for SCCM restart (exit code: $intendedExitCode)" "Cyan" - } - - $restartTime = [DateTimeOffset]::Now.AddMinutes($rebootDelay).ToUnixTimeSeconds() - # Ensure registry path exists if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null } - # Set restart properties for SCCM - New-ItemProperty -Path $regPath -Name 'RebootBy' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'HardReboot' -Value $hardRebootValue -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null + # Check the intended exit code to determine restart type + $intendedExitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } + + if ($intendedExitCode -eq 1641) { + # HARD/MANDATORY REBOOT in SCCM registry (show UI to user, doesn't execute automatically!) + $restartTime = [DateTimeOffset]::Now.AddMinutes($rebootDelay).ToUnixTimeSeconds() + + # CRITICAL: Both RebootBy and OverrideRebootWindowTime must be set to the same value + New-ItemProperty -Path $regPath -Name 'RebootBy' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'OverrideRebootWindowTime' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null + + # Mandatory reboot settings + New-ItemProperty -Path $regPath -Name 'PreferredRebootWindowTypes' -Value @("3") -PropertyType MultiString -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'OverrideRebootWindow' -Value 1 -PropertyType DWord -Force | Out-Null + + # Ignore service window settings + New-ItemProperty -Path $regPath -Name 'OverrideServiceWindows' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootOutsideOfServiceWindow' -Value 1 -PropertyType DWord -Force | Out-Null + + # Hard reboot settings + New-ItemProperty -Path $regPath -Name 'HardReboot' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force | Out-Null + + # HARD/MANDATORY REBOOT via Task Scheduler (for execution, unless user executed it manually via UI) + $taskName = "WAU_MandatoryRestart" + $taskPath = "\WAU\" + + # Create a self destroying scheduled task for mandatory restart + Write-ToLog "Creating scheduled task for mandatory restart in $rebootDelay minutes" "Yellow" + $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument @" +-NoProfile -WindowStyle Hidden -Command " +`$regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' +# Only run if RebootBy and OverrideRebootWindowTime exists under the key (user has already restarted the client) +`$regProps = Get-ItemProperty -Path `$regPath -ErrorAction SilentlyContinue +if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSObject.Properties.Name -contains 'OverrideRebootWindowTime') { + if (-not (Test-Path `$regPath)) { New-Item -Path `$regPath -Force } + 'RebootBy','OverrideRebootWindowTime' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value ([Int64]-1) -PropertyType QWord -Force + } + 'PreferredRebootWindowTypes' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value @('3') -PropertyType MultiString -Force + } + 'OverrideRebootWindow','HardReboot','NotifyUI','RebootValueInUTC','SetTime','OverrideServiceWindows','RebootOutsideOfServiceWindow' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value 1 -PropertyType DWord -Force + } + New-ItemProperty -Path `$regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force + Start-Process -FilePath "`$env:windir\CCM\CcmRestart.exe" -NoNewWindow -Wait +} +" +"@ + $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($rebootDelay) + # Set EndBoundary to make DeleteExpiredTaskAfter work + $trigger.EndBoundary = (Get-Date).AddMinutes($rebootDelay).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") + $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) + $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest + Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Mandatory Restart by SCCM" -Force | Out-Null + } else { + # SOFT/NON-MANDATORY REBOOT + Write-ToLog "Using soft reboot (non-mandatory) for SCCM restart" "Cyan" + + # For non-mandatory, RebootBy should be 0 to show dialog immediately + New-ItemProperty -Path $regPath -Name 'RebootBy' -Value 0 -PropertyType QWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'OverrideRebootWindowTime' -Value 0 -PropertyType QWord -Force | Out-Null + + # Set as non-mandatory reboot + New-ItemProperty -Path $regPath -Name 'PreferredRebootWindowTypes' -Value @("4") -PropertyType MultiString -Force | Out-Null + + # Soft reboot settings + New-ItemProperty -Path $regPath -Name 'HardReboot' -Value 0 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'GraceSeconds' -Value 300 -PropertyType DWord -Force | Out-Null # Default grace period of 5 minutes + } # Try CcmRestart.exe first for notification if (Test-Path $ccmRestartPath) { @@ -485,7 +543,11 @@ if (Test-Network) { Restart-Service ccmexec -Force -ErrorAction SilentlyContinue } - Write-ToLog "SCCM managed restart scheduled for: $([DateTimeOffset]::FromUnixTimeSeconds($restartTime).LocalDateTime)" "Green" + if ($intendedExitCode -eq 1641) { + Write-ToLog "MANDATORY restart via scheduled task: In $rebootDelay minutes" "Green" + } else { + Write-ToLog "Non-mandatory restart dialog triggered" "Green" + } } } catch { diff --git a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 index a09509b7a..558f5af66 100644 --- a/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 +++ b/Sources/Winget-AutoUpdate/mods/_WAU-mods-template.ps1 @@ -78,7 +78,7 @@ Action = "Reboot" Message = "The system needs to reboot within 15 minutes before WAU updates can be performed." LogLevel = "Red" - ExitCode = 3010 + ExitCode = 1641 # Optional: Use 1641 for SCCM Hard Reboot (default is 3010 for Soft Reboot) RebootDelay = 15 # Optional: Delay before rebooting (default is 5 minutes) RebootHandler = "SCCM" # Optional: Specify reboot handler (default is "Windows") } | ConvertTo-Json -Compress From a7ce3f132e71668395a1fdd5477b25ba6dfd98a7 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Thu, 26 Jun 2025 23:47:33 +0200 Subject: [PATCH 26/49] Enhance mandatory restart (SCCM: Hard Reboot) handling with improved logging and error checks --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 53 +++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index b6a05e4d0..53e763035 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -488,13 +488,32 @@ if (Test-Network) { # Create a self destroying scheduled task for mandatory restart Write-ToLog "Creating scheduled task for mandatory restart in $rebootDelay minutes" "Yellow" - $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument @" --NoProfile -WindowStyle Hidden -Command " + + # Create PowerShell script with enhanced logging + $scriptContent = @" `$regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -# Only run if RebootBy and OverrideRebootWindowTime exists under the key (user has already restarted the client) +`$ccmRestartPath = "`$env:windir\CCM\CcmRestart.exe" +`$logPath = "$($Script:WorkingDir)\logs\mandatory_restart.log" + +# Function to write to log +function Write-RestartLog { + param([string]`$Message) + `$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + "`$timestamp - `$Message" | Out-File -FilePath `$logPath -Append -Encoding UTF8 +} + +Write-RestartLog "Mandatory restart task started" + +# Only run if RebootBy and OverrideRebootWindowTime exists under the key (if not: the user has already restarted the client) `$regProps = Get-ItemProperty -Path `$regPath -ErrorAction SilentlyContinue if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSObject.Properties.Name -contains 'OverrideRebootWindowTime') { - if (-not (Test-Path `$regPath)) { New-Item -Path `$regPath -Force } + Write-RestartLog "SCCM restart registry values found, proceeding with restart" + + if (-not (Test-Path `$regPath)) { + New-Item -Path `$regPath -Force + Write-RestartLog "Created registry path: `$regPath" + } + 'RebootBy','OverrideRebootWindowTime' | ForEach-Object { New-ItemProperty -Path `$regPath -Name `$_ -Value ([Int64]-1) -PropertyType QWord -Force } @@ -505,10 +524,32 @@ if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSO New-ItemProperty -Path `$regPath -Name `$_ -Value 1 -PropertyType DWord -Force } New-ItemProperty -Path `$regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force - Start-Process -FilePath "`$env:windir\CCM\CcmRestart.exe" -NoNewWindow -Wait + + Write-RestartLog "Registry values updated for mandatory restart" + + # Check if CcmRestart.exe exists and use it, otherwise restart the service + if (Test-Path `$ccmRestartPath) { + Write-RestartLog "Executing CcmRestart.exe" + Start-Process -FilePath `$ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + Write-RestartLog "CcmRestart.exe execution completed" + } else { + Write-RestartLog "CcmRestart.exe not found, restarting ccmexec service" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + Write-RestartLog "ccmexec service restart completed" + } +} else { + Write-RestartLog "No SCCM restart registry values found, task completed without action" } -" + +Write-RestartLog "Mandatory restart task completed" "@ + + # Encode to Base64 + $encodedScript = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptContent)) + + # Create action with encoded command + $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -EncodedCommand $encodedScript" + $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($rebootDelay) # Set EndBoundary to make DeleteExpiredTaskAfter work $trigger.EndBoundary = (Get-Date).AddMinutes($rebootDelay).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") From c3af9d77e50d5ec409022c44585d2a62d60a1924 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Fri, 27 Jun 2025 13:03:13 +0200 Subject: [PATCH 27/49] Refactoring with external 'Test-WAUMods.ps1' --- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 331 +---------------- .../functions/Test-WAUMods.ps1 | 340 ++++++++++++++++++ 2 files changed, 344 insertions(+), 327 deletions(-) create mode 100644 Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 53e763035..998aa9013 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -299,334 +299,11 @@ if (Test-Network) { } } - #Test if _WAU-mods.ps1 exist: Mods for WAU (if Network is active/any Winget is installed/running as SYSTEM) + # Test if _WAU-mods.ps1 exist: Mods for WAU (if Network is active/any Winget is installed/running as SYSTEM) $Mods = "$WorkingDir\mods" if (Test-Path "$Mods\_WAU-mods.ps1") { - Write-ToLog "Running Mods for WAU..." "Cyan" - - # Capture both output and exit code - $ModsOutput = & "$Mods\_WAU-mods.ps1" 2>&1 | Out-String - $ModsExitCode = $LASTEXITCODE - - # Handle legacy exit code behavior first (backward compatibility) - if ($ModsExitCode -eq 1) { - Write-ToLog "Legacy exit code 1 detected - Re-running WAU" - Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" - Exit - } - - # Try to parse JSON output for new action-based system - if ($ModsOutput -and $ModsOutput.Trim()) { - try { - # Remove any non-JSON content (like debug output) and find JSON - $jsonMatch = $ModsOutput | Select-String -Pattern '\{.*\}' | Select-Object -First 1 - - if ($jsonMatch) { - $ModsResult = $jsonMatch.Matches[0].Value | ConvertFrom-Json - - # Log message if provided - if ($ModsResult.Message) { - $logLevel = if ($ModsResult.LogLevel) { $ModsResult.LogLevel } else { "White" } - Write-ToLog $ModsResult.Message $logLevel - } - - # Execute action based on returned instruction - switch ($ModsResult.Action) { - "Rerun" { - Write-ToLog "Mods requested a WAU re-run" - Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\winget-upgrade.ps1`"" - $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 0 } - Exit $exitCode - } - "Abort" { - Write-ToLog "Mods requested WAU to abort" - $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" - Exit $exitCode - } - "Postpone" { - Write-ToLog "Mods requested a postpone of WAU" - # Check if a postponed task already exists - $existingTask = Get-ScheduledTask -TaskPath "\WAU\" -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like "Postponed-$($Script:GitHub_Repo)*" } - if ($existingTask) { - Write-ToLog "A postponed task for $($Script:GitHub_Repo) already exists, not creating another." "Yellow" - } - else { - # Get configurable duration, default to 1 hour - $postponeDuration = if ($ModsResult.PostponeDuration) { - try { - [double]$parsedDuration = [double]$ModsResult.PostponeDuration - # Ensure minimum duration of 0.1 hours (6 minutes) - if ($parsedDuration -lt 0.1) { - Write-ToLog "PostponeDuration adjusted to minimum 0.1 hours (6 minutes)" "Yellow" - 0.1 - } else { - $parsedDuration - } - } - catch { - Write-ToLog "Invalid PostponeDuration value '$($ModsResult.PostponeDuration)', using default 1 hour" "Yellow" - 1 - } - } else { - 1 - } - - # Create a postponed temporary scheduled task to try again later - $uniqueTaskName = "Postponed-$($Script:GitHub_Repo)_$(Get-Random)" - $taskPath = "\WAU\" - $copyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($WAUConfig.InstallLocation)Winget-Upgrade.ps1`"" - $copyTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours($postponeDuration) - # Set EndBoundary to make DeleteExpiredTaskAfter work - $copyTrigger.EndBoundary = (Get-Date).AddHours($postponeDuration).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") - $copySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) - $copyPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - Register-ScheduledTask -TaskName $uniqueTaskName -TaskPath $taskPath -Action $copyAction -Trigger $copyTrigger -Settings $copySettings -Principal $copyPrincipal -Description "Postponed copy of $Script:GitHub_Repo" | Out-Null - Write-ToLog "WAU will try again in $postponeDuration hours" "Yellow" - } - $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" - Exit $exitCode - } - "Reboot" { - Write-ToLog "Mods requested a system reboot" - # Get configurable delay, default to 5 minutes - $rebootDelay = if ($ModsResult.RebootDelay) { - try { - [double]$parsedDelay = [double]$ModsResult.RebootDelay - # Ensure minimum delay of 1 minute for safety - if ($parsedDelay -lt 1) { - Write-ToLog "RebootDelay adjusted to minimum 1 minute" "Yellow" - 1 - } else { - $parsedDelay - } - } - catch { - Write-ToLog "Invalid RebootDelay value '$($ModsResult.RebootDelay)', using default 5 minutes" "Yellow" - 5 - } - } else { - 5 - } - - $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay minutes" } - $rebootHandler = if ($ModsResult.RebootHandler) { $ModsResult.RebootHandler } else { "Windows" } - - # Check if SCCM client is available for managed restart (user controlled) - $sccmClient = Get-CimInstance -Namespace "root\ccm" -ClassName "SMS_Client" -ErrorAction SilentlyContinue - - if ($sccmClient -and ($rebootHandler -eq "SCCM")) { - Write-ToLog "SCCM client detected - using managed restart (user controlled)" "Green" - - $ccmRestartPath = "$env:windir\CCM\CcmRestart.exe" - $regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' - - try { - # Check if SCCM restart registry values already exist - $existingRebootBy = $null - $existingRebootValues = $false - - if (Test-Path $regPath) { - $existingRebootBy = Get-ItemProperty -Path $regPath -Name 'RebootBy' -ErrorAction SilentlyContinue - $existingNotifyUI = Get-ItemProperty -Path $regPath -Name 'NotifyUI' -ErrorAction SilentlyContinue - $existingSetTime = Get-ItemProperty -Path $regPath -Name 'SetTime' -ErrorAction SilentlyContinue - - # Check if we have the key registry values indicating a restart is already scheduled - if ($existingRebootBy -and $existingNotifyUI -and $existingSetTime -and $existingRebootBy.PSObject.Properties['RebootBy']) { - $existingRebootValues = $true - $existingRestartTime = [DateTimeOffset]::FromUnixTimeSeconds([int64]$existingRebootBy.RebootBy).LocalDateTime - Write-ToLog "SCCM restart already scheduled for: $existingRestartTime" "Yellow" - } - } - - if ($existingRebootValues) { - # Try CcmRestart.exe for notification - if (Test-Path $ccmRestartPath) { - Write-ToLog "Triggering SCCM restart notification via CcmRestart.exe" "Cyan" - Start-Process -FilePath $ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue - } else { - Write-ToLog "CcmRestart.exe not found, restarting ccmexec service" "Yellow" - Restart-Service ccmexec -Force -ErrorAction SilentlyContinue - } - } else { - # No existing restart scheduled - create new SCCM managed restart (user controlled) - Write-ToLog "Setting up new SCCM managed restart schedule" "Green" - - # Ensure registry path exists - if (-not (Test-Path $regPath)) { - New-Item -Path $regPath -Force | Out-Null - } - - # Check the intended exit code to determine restart type - $intendedExitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } - - if ($intendedExitCode -eq 1641) { - # HARD/MANDATORY REBOOT in SCCM registry (show UI to user, doesn't execute automatically!) - $restartTime = [DateTimeOffset]::Now.AddMinutes($rebootDelay).ToUnixTimeSeconds() - - # CRITICAL: Both RebootBy and OverrideRebootWindowTime must be set to the same value - New-ItemProperty -Path $regPath -Name 'RebootBy' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'OverrideRebootWindowTime' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null - - # Mandatory reboot settings - New-ItemProperty -Path $regPath -Name 'PreferredRebootWindowTypes' -Value @("3") -PropertyType MultiString -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'OverrideRebootWindow' -Value 1 -PropertyType DWord -Force | Out-Null - - # Ignore service window settings - New-ItemProperty -Path $regPath -Name 'OverrideServiceWindows' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'RebootOutsideOfServiceWindow' -Value 1 -PropertyType DWord -Force | Out-Null - - # Hard reboot settings - New-ItemProperty -Path $regPath -Name 'HardReboot' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force | Out-Null - - # HARD/MANDATORY REBOOT via Task Scheduler (for execution, unless user executed it manually via UI) - $taskName = "WAU_MandatoryRestart" - $taskPath = "\WAU\" - - # Create a self destroying scheduled task for mandatory restart - Write-ToLog "Creating scheduled task for mandatory restart in $rebootDelay minutes" "Yellow" - - # Create PowerShell script with enhanced logging - $scriptContent = @" -`$regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -`$ccmRestartPath = "`$env:windir\CCM\CcmRestart.exe" -`$logPath = "$($Script:WorkingDir)\logs\mandatory_restart.log" - -# Function to write to log -function Write-RestartLog { - param([string]`$Message) - `$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' - "`$timestamp - `$Message" | Out-File -FilePath `$logPath -Append -Encoding UTF8 -} - -Write-RestartLog "Mandatory restart task started" - -# Only run if RebootBy and OverrideRebootWindowTime exists under the key (if not: the user has already restarted the client) -`$regProps = Get-ItemProperty -Path `$regPath -ErrorAction SilentlyContinue -if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSObject.Properties.Name -contains 'OverrideRebootWindowTime') { - Write-RestartLog "SCCM restart registry values found, proceeding with restart" - - if (-not (Test-Path `$regPath)) { - New-Item -Path `$regPath -Force - Write-RestartLog "Created registry path: `$regPath" - } - - 'RebootBy','OverrideRebootWindowTime' | ForEach-Object { - New-ItemProperty -Path `$regPath -Name `$_ -Value ([Int64]-1) -PropertyType QWord -Force - } - 'PreferredRebootWindowTypes' | ForEach-Object { - New-ItemProperty -Path `$regPath -Name `$_ -Value @('3') -PropertyType MultiString -Force - } - 'OverrideRebootWindow','HardReboot','NotifyUI','RebootValueInUTC','SetTime','OverrideServiceWindows','RebootOutsideOfServiceWindow' | ForEach-Object { - New-ItemProperty -Path `$regPath -Name `$_ -Value 1 -PropertyType DWord -Force - } - New-ItemProperty -Path `$regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force - - Write-RestartLog "Registry values updated for mandatory restart" - - # Check if CcmRestart.exe exists and use it, otherwise restart the service - if (Test-Path `$ccmRestartPath) { - Write-RestartLog "Executing CcmRestart.exe" - Start-Process -FilePath `$ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue - Write-RestartLog "CcmRestart.exe execution completed" - } else { - Write-RestartLog "CcmRestart.exe not found, restarting ccmexec service" - Restart-Service ccmexec -Force -ErrorAction SilentlyContinue - Write-RestartLog "ccmexec service restart completed" - } -} else { - Write-RestartLog "No SCCM restart registry values found, task completed without action" -} - -Write-RestartLog "Mandatory restart task completed" -"@ - - # Encode to Base64 - $encodedScript = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptContent)) - - # Create action with encoded command - $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -EncodedCommand $encodedScript" - - $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($rebootDelay) - # Set EndBoundary to make DeleteExpiredTaskAfter work - $trigger.EndBoundary = (Get-Date).AddMinutes($rebootDelay).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") - $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) - $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Mandatory Restart by SCCM" -Force | Out-Null - } else { - # SOFT/NON-MANDATORY REBOOT - Write-ToLog "Using soft reboot (non-mandatory) for SCCM restart" "Cyan" - - # For non-mandatory, RebootBy should be 0 to show dialog immediately - New-ItemProperty -Path $regPath -Name 'RebootBy' -Value 0 -PropertyType QWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'OverrideRebootWindowTime' -Value 0 -PropertyType QWord -Force | Out-Null - - # Set as non-mandatory reboot - New-ItemProperty -Path $regPath -Name 'PreferredRebootWindowTypes' -Value @("4") -PropertyType MultiString -Force | Out-Null - - # Soft reboot settings - New-ItemProperty -Path $regPath -Name 'HardReboot' -Value 0 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null - New-ItemProperty -Path $regPath -Name 'GraceSeconds' -Value 300 -PropertyType DWord -Force | Out-Null # Default grace period of 5 minutes - } - - # Try CcmRestart.exe first for notification - if (Test-Path $ccmRestartPath) { - Write-ToLog "Triggering SCCM restart notification via CcmRestart.exe" "Cyan" - Start-Process -FilePath $ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue - } else { - Write-ToLog "CcmRestart.exe not found, restarting ccmexec service" "Yellow" - Restart-Service ccmexec -Force -ErrorAction SilentlyContinue - } - - if ($intendedExitCode -eq 1641) { - Write-ToLog "MANDATORY restart via scheduled task: In $rebootDelay minutes" "Green" - } else { - Write-ToLog "Non-mandatory restart dialog triggered" "Green" - } - } - } - catch { - Write-ToLog "Failed to set SCCM restart: $($_.Exception.Message). Falling back to standard restart." "Yellow" - # Fallback to standard shutdown - $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 - if ($LASTEXITCODE -eq 0) { - Write-ToLog "System restart scheduled in $rebootDelay minutes (fallback)" "Yellow" - } else { - Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" - } - } - } else { - # Standard shutdown when SCCM is not available (or reboot handler is not "SCCM") - $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 - if ($LASTEXITCODE -eq 0) { - Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" - } else { - Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" - } - } - $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 3010 } # Default to "Restart required" - Exit $exitCode - } - "Continue" { - Write-ToLog "Mods allows WAU to continue normally" - } - default { - Write-ToLog "Unknown action '$($ModsResult.Action)' from mods, continuing normally" "Cyan" - } - } - } - } - catch { - Write-ToLog "Failed to parse mods JSON output: $($_.Exception.Message)" "Red" - Write-ToLog "Continuing with normal WAU execution" "Cyan" - } - } + Write-ToLog "Running Mods for WAU..." "DarkYellow" + Test-WAUMods -WorkingDir $WorkingDir -WAUConfig $WAUConfig -GitHub_Repo $GitHub_Repo } } @@ -749,7 +426,7 @@ Write-RestartLog "Mandatory restart task completed" Write-ToLog "No new update." "Green" } - #Test if _WAU-mods-postsys.ps1 exists: Mods for WAU (postsys) - if Network is active/any Winget is installed/running as SYSTEM _after_ SYSTEM updates + # Test if _WAU-mods-postsys.ps1 exists: Mods for WAU (postsys) - if Network is active/any Winget is installed/running as SYSTEM _after_ SYSTEM updates if ($true -eq $IsSystem) { if (Test-Path "$Mods\_WAU-mods-postsys.ps1") { Write-ToLog "Running Mods (postsys) for WAU..." "DarkYellow" diff --git a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 new file mode 100644 index 000000000..b4b7859de --- /dev/null +++ b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 @@ -0,0 +1,340 @@ +function Test-WAUMods { + param ( + [Parameter(Mandatory=$true)] + [string]$WorkingDir, + + [Parameter(Mandatory=$true)] + [PSCustomObject]$WAUConfig, + + [Parameter(Mandatory=$false)] + [string]$GitHub_Repo = "Winget-AutoUpdate" + ) + + # Define Mods path (for independent execution just in case) + $Mods = "$WorkingDir\mods" + + # Capture both output and exit code + $ModsOutput = & "$Mods\_WAU-mods.ps1" 2>&1 | Out-String + $ModsExitCode = $LASTEXITCODE + + # Handle legacy exit code behavior first (backward compatibility) + if ($ModsExitCode -eq 1) { + Write-ToLog "Legacy exit code 1 detected - Re-running WAU" + Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\Winget-Upgrade.ps1`"" + Exit + } + + # Try to parse JSON output for new action-based system + if ($ModsOutput -and $ModsOutput.Trim()) { + try { + # Remove any non-JSON content (like debug output) and find JSON + $jsonMatch = $ModsOutput | Select-String -Pattern '\{.*\}' | Select-Object -First 1 + + if ($jsonMatch) { + $ModsResult = $jsonMatch.Matches[0].Value | ConvertFrom-Json + + # Log message if provided + if ($ModsResult.Message) { + $logLevel = if ($ModsResult.LogLevel) { $ModsResult.LogLevel } else { "White" } + Write-ToLog $ModsResult.Message $logLevel + } + + # Execute action based on returned instruction + switch ($ModsResult.Action) { + "Rerun" { + Write-ToLog "Mods requested a WAU re-run" + Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$WorkingDir\Winget-Upgrade.ps1`"" + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 0 } + Exit $exitCode + } + "Abort" { + Write-ToLog "Mods requested WAU to abort" + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" + Exit $exitCode + } + "Postpone" { + Write-ToLog "Mods requested a postpone of WAU" + # Check if a postponed task already exists + $existingTask = Get-ScheduledTask -TaskPath "\WAU\" -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like "Postponed-$($GitHub_Repo)*" } + if ($existingTask) { + Write-ToLog "A postponed task for $($GitHub_Repo) already exists, not creating another." "Yellow" + } + else { + # Get configurable duration, default to 1 hour + $postponeDuration = if ($ModsResult.PostponeDuration) { + try { + [double]$parsedDuration = [double]$ModsResult.PostponeDuration + # Ensure minimum duration of 0.1 hours (6 minutes) + if ($parsedDuration -lt 0.1) { + Write-ToLog "PostponeDuration adjusted to minimum 0.1 hours (6 minutes)" "Yellow" + 0.1 + } else { + $parsedDuration + } + } + catch { + Write-ToLog "Invalid PostponeDuration value '$($ModsResult.PostponeDuration)', using default 1 hour" "Yellow" + 1 + } + } else { + 1 + } + + # Create a postponed temporary scheduled task to try again later + $uniqueTaskName = "Postponed-$($GitHub_Repo)_$(Get-Random)" + $taskPath = "\WAU\" + $copyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($WAUConfig.InstallLocation)Winget-Upgrade.ps1`"" + $copyTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours($postponeDuration) + # Set EndBoundary to make DeleteExpiredTaskAfter work + $copyTrigger.EndBoundary = (Get-Date).AddHours($postponeDuration).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") + $copySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) + $copyPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest + Register-ScheduledTask -TaskName $uniqueTaskName -TaskPath $taskPath -Action $copyAction -Trigger $copyTrigger -Settings $copySettings -Principal $copyPrincipal -Description "Postponed copy of $($GitHub_Repo)" | Out-Null + Write-ToLog "WAU will try again in $postponeDuration hours" "Yellow" + } + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 1602 } # Default to "User cancelled" + Exit $exitCode + } + "Reboot" { + Write-ToLog "Mods requested a system reboot" + # Get configurable delay, default to 5 minutes + $rebootDelay = if ($ModsResult.RebootDelay) { + try { + [double]$parsedDelay = [double]$ModsResult.RebootDelay + # Ensure minimum delay of 1 minute for safety + if ($parsedDelay -lt 1) { + Write-ToLog "RebootDelay adjusted to minimum 1 minute" "Yellow" + 1 + } else { + $parsedDelay + } + } + catch { + Write-ToLog "Invalid RebootDelay value '$($ModsResult.RebootDelay)', using default 5 minutes" "Yellow" + 5 + } + } else { + 5 + } + + $shutdownMessage = if ($ModsResult.Message) { $ModsResult.Message } else { "WAU Mods requested a system reboot in $rebootDelay minutes" } + $rebootHandler = if ($ModsResult.RebootHandler) { $ModsResult.RebootHandler } else { "Windows" } + + # Check if SCCM client is available for managed restart (user controlled) + $sccmClient = Get-CimInstance -Namespace "root\ccm" -ClassName "SMS_Client" -ErrorAction SilentlyContinue + + if ($sccmClient -and ($rebootHandler -eq "SCCM")) { + Write-ToLog "SCCM client detected - using managed restart (user controlled)" "Green" + + $ccmRestartPath = "$env:windir\CCM\CcmRestart.exe" + $regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' + + try { + # Check if SCCM restart registry values already exist + $existingRebootBy = $null + $existingRebootValues = $false + + if (Test-Path $regPath) { + $existingRebootBy = Get-ItemProperty -Path $regPath -Name 'RebootBy' -ErrorAction SilentlyContinue + $existingNotifyUI = Get-ItemProperty -Path $regPath -Name 'NotifyUI' -ErrorAction SilentlyContinue + $existingSetTime = Get-ItemProperty -Path $regPath -Name 'SetTime' -ErrorAction SilentlyContinue + + # Check if we have the key registry values indicating a restart is already scheduled + if ($existingRebootBy -and $existingNotifyUI -and $existingSetTime -and $existingRebootBy.PSObject.Properties['RebootBy']) { + $existingRebootValues = $true + $existingRestartTime = [DateTimeOffset]::FromUnixTimeSeconds([int64]$existingRebootBy.RebootBy).LocalDateTime + Write-ToLog "SCCM restart already scheduled for: $existingRestartTime" "Yellow" + } + } + + if ($existingRebootValues) { + # Try CcmRestart.exe for notification + if (Test-Path $ccmRestartPath) { + Write-ToLog "Triggering SCCM restart notification via CcmRestart.exe" "Cyan" + Start-Process -FilePath $ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + } else { + Write-ToLog "CcmRestart.exe not found, restarting ccmexec service" "Yellow" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + } + } else { + # No existing restart scheduled - create new SCCM managed restart (user controlled) + Write-ToLog "Setting up new SCCM managed restart schedule" "Green" + + # Ensure registry path exists + if (-not (Test-Path $regPath)) { + New-Item -Path $regPath -Force | Out-Null + } + + # Check the intended exit code to determine restart type + $intendedExitCode = if ($ModsResult.ExitCode) { $ModsResult.ExitCode } else { 3010 } + + if ($intendedExitCode -eq 1641) { + # HARD/MANDATORY REBOOT in SCCM registry (show UI to user, doesn't execute automatically!) + $restartTime = [DateTimeOffset]::Now.AddMinutes($rebootDelay).ToUnixTimeSeconds() + + # CRITICAL: Both RebootBy and OverrideRebootWindowTime must be set to the same value + New-ItemProperty -Path $regPath -Name 'RebootBy' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'OverrideRebootWindowTime' -Value ([Int64]$restartTime) -PropertyType QWord -Force | Out-Null + + # Mandatory reboot settings + New-ItemProperty -Path $regPath -Name 'PreferredRebootWindowTypes' -Value @("3") -PropertyType MultiString -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'OverrideRebootWindow' -Value 1 -PropertyType DWord -Force | Out-Null + + # Ignore service window settings + New-ItemProperty -Path $regPath -Name 'OverrideServiceWindows' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootOutsideOfServiceWindow' -Value 1 -PropertyType DWord -Force | Out-Null + + # Hard reboot settings + New-ItemProperty -Path $regPath -Name 'HardReboot' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force | Out-Null + + # HARD/MANDATORY REBOOT via Task Scheduler (for execution, unless user executed it manually via UI) + $taskName = "WAU_MandatoryRestart" + $taskPath = "\WAU\" + + # Create a self destroying scheduled task for mandatory restart + Write-ToLog "Creating scheduled task for mandatory restart in $rebootDelay minutes" "Yellow" + + # Create PowerShell script with enhanced logging + $scriptContent = @" +`$regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' +`$ccmRestartPath = "`$env:windir\CCM\CcmRestart.exe" +`$logPath = "$WorkingDir\logs\mandatory_restart.log" + +# Function to write to log +function Write-RestartLog { + param([string]`$Message) + `$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + "`$timestamp - `$Message" | Out-File -FilePath `$logPath -Append -Encoding UTF8 +} + +Write-RestartLog "Mandatory restart task started" + +# Only run if RebootBy and OverrideRebootWindowTime exists under the key (if not: the user has already restarted the client) +`$regProps = Get-ItemProperty -Path `$regPath -ErrorAction SilentlyContinue +if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSObject.Properties.Name -contains 'OverrideRebootWindowTime') { + Write-RestartLog "SCCM restart registry values found, proceeding with restart" + + if (-not (Test-Path `$regPath)) { + New-Item -Path `$regPath -Force + Write-RestartLog "Created registry path: `$regPath" + } + + 'RebootBy','OverrideRebootWindowTime' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value ([Int64]-1) -PropertyType QWord -Force + } + 'PreferredRebootWindowTypes' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value @('3') -PropertyType MultiString -Force + } + 'OverrideRebootWindow','HardReboot','NotifyUI','RebootValueInUTC','SetTime','OverrideServiceWindows','RebootOutsideOfServiceWindow' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value 1 -PropertyType DWord -Force + } + New-ItemProperty -Path `$regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force + + Write-RestartLog "Registry values updated for mandatory restart" + + # Check if CcmRestart.exe exists and use it, otherwise restart the service + if (Test-Path `$ccmRestartPath) { + Write-RestartLog "Executing CcmRestart.exe" + Start-Process -FilePath `$ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + Write-RestartLog "CcmRestart.exe execution completed" + } else { + Write-RestartLog "CcmRestart.exe not found, restarting ccmexec service" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + Write-RestartLog "ccmexec service restart completed" + } +} else { + Write-RestartLog "No SCCM restart registry values found, task completed without action" +} + +Write-RestartLog "Mandatory restart task completed" +"@ + + # Encode to Base64 + $encodedScript = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptContent)) + + # Create action with encoded command + $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -EncodedCommand $encodedScript" + + $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($rebootDelay) + # Set EndBoundary to make DeleteExpiredTaskAfter work + $trigger.EndBoundary = (Get-Date).AddMinutes($rebootDelay).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") + $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) + $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest + Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Mandatory Restart by SCCM" -Force | Out-Null + } else { + # SOFT/NON-MANDATORY REBOOT + Write-ToLog "Using soft reboot (non-mandatory) for SCCM restart" "Cyan" + + # For non-mandatory, RebootBy should be 0 to show dialog immediately + New-ItemProperty -Path $regPath -Name 'RebootBy' -Value 0 -PropertyType QWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'OverrideRebootWindowTime' -Value 0 -PropertyType QWord -Force | Out-Null + + # Set as non-mandatory reboot + New-ItemProperty -Path $regPath -Name 'PreferredRebootWindowTypes' -Value @("4") -PropertyType MultiString -Force | Out-Null + + # Soft reboot settings + New-ItemProperty -Path $regPath -Name 'HardReboot' -Value 0 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'SetTime' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $regPath -Name 'GraceSeconds' -Value 300 -PropertyType DWord -Force | Out-Null # Default grace period of 5 minutes + } + + # Try CcmRestart.exe first for notification + if (Test-Path $ccmRestartPath) { + Write-ToLog "Triggering SCCM restart notification via CcmRestart.exe" "Cyan" + Start-Process -FilePath $ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + } else { + Write-ToLog "CcmRestart.exe not found, restarting ccmexec service" "Yellow" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + } + + if ($intendedExitCode -eq 1641) { + Write-ToLog "MANDATORY restart via scheduled task: In $rebootDelay minutes" "Green" + } else { + Write-ToLog "Non-mandatory restart dialog triggered" "Green" + } + } + } + catch { + Write-ToLog "Failed to set SCCM restart: $($_.Exception.Message). Falling back to standard restart." "Yellow" + # Fallback to standard shutdown + $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-ToLog "System restart scheduled in $rebootDelay minutes (fallback)" "Yellow" + } else { + Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" + } + } + } else { + # Standard shutdown when SCCM is not available (or reboot handler is not "SCCM") + $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" + } else { + Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" + } + } + $exitCode = if ($ModsResult.ExitCode) { [int]$ModsResult.ExitCode } else { 3010 } # Default to "Restart required" + Exit $exitCode + } + "Continue" { + Write-ToLog "Mods allows WAU to continue normally" + } + default { + Write-ToLog "Unknown action '$($ModsResult.Action)' from mods, continuing normally" "Cyan" + } + } + } + } + catch { + Write-ToLog "Failed to parse mods JSON output: $($_.Exception.Message)" "Red" + Write-ToLog "Continuing with normal WAU execution" "Cyan" + } + } + +} \ No newline at end of file From 8f7f279491014ea463396eafb72d6384e602eaf7 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Fri, 27 Jun 2025 14:29:33 +0200 Subject: [PATCH 28/49] Rewording comment for clarity --- Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 index b4b7859de..5b55a0bd4 100644 --- a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 +++ b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 @@ -10,7 +10,7 @@ function Test-WAUMods { [string]$GitHub_Repo = "Winget-AutoUpdate" ) - # Define Mods path (for independent execution just in case) + # Define Mods path $Mods = "$WorkingDir\mods" # Capture both output and exit code From 3a3ede77e1ef1e0dddf74e8854abb477d123d877 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sat, 28 Jun 2025 08:00:32 +0200 Subject: [PATCH 29/49] Hybrid mandatory SCCM restart handling --- .../functions/Test-WAUMods.ps1 | 82 +++++++++++++------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 index 5b55a0bd4..08685bb07 100644 --- a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 +++ b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 @@ -198,11 +198,16 @@ function Test-WAUMods { # Create a self destroying scheduled task for mandatory restart Write-ToLog "Creating scheduled task for mandatory restart in $rebootDelay minutes" "Yellow" + # Escape the shutdown message properly before using in here-string + $escapedShutdownMessage = $shutdownMessage -replace '"', '\"' -replace '`', '``' + # Create PowerShell script with enhanced logging $scriptContent = @" `$regPath = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' `$ccmRestartPath = "`$env:windir\CCM\CcmRestart.exe" `$logPath = "$WorkingDir\logs\mandatory_restart.log" +`$rebootDelay = $rebootDelay +`$shutdownMessage = "$escapedShutdownMessage" # Function to write to log function Write-RestartLog { @@ -218,33 +223,58 @@ Write-RestartLog "Mandatory restart task started" if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSObject.Properties.Name -contains 'OverrideRebootWindowTime') { Write-RestartLog "SCCM restart registry values found, proceeding with restart" - if (-not (Test-Path `$regPath)) { - New-Item -Path `$regPath -Force - Write-RestartLog "Created registry path: `$regPath" - } - - 'RebootBy','OverrideRebootWindowTime' | ForEach-Object { - New-ItemProperty -Path `$regPath -Name `$_ -Value ([Int64]-1) -PropertyType QWord -Force - } - 'PreferredRebootWindowTypes' | ForEach-Object { - New-ItemProperty -Path `$regPath -Name `$_ -Value @('3') -PropertyType MultiString -Force - } - 'OverrideRebootWindow','HardReboot','NotifyUI','RebootValueInUTC','SetTime','OverrideServiceWindows','RebootOutsideOfServiceWindow' | ForEach-Object { - New-ItemProperty -Path `$regPath -Name `$_ -Value 1 -PropertyType DWord -Force - } - New-ItemProperty -Path `$regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force - - Write-RestartLog "Registry values updated for mandatory restart" - - # Check if CcmRestart.exe exists and use it, otherwise restart the service - if (Test-Path `$ccmRestartPath) { - Write-RestartLog "Executing CcmRestart.exe" - Start-Process -FilePath `$ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue - Write-RestartLog "CcmRestart.exe execution completed" + # Grace period is over, system will now restart in 2 minutes + Write-RestartLog "The grace period for restart (`$rebootDelay minutes) is over (`$shutdownMessage). System will restart in 2 minutes." "Yellow" + + `$result = & shutdown /r /t 120 /c "Mandatory restart: The grace period for restart (`$rebootDelay minutes) is over (`$shutdownMessage). System will restart in 2 minutes." 2>&1 + if (`$LASTEXITCODE -eq 0) { + Write-RestartLog "System restart scheduled in 2 minutes" "Yellow" + + # Remove all values under the registry key + `$key = Get-Item -Path `$regPath -ErrorAction SilentlyContinue + if (`$key) { + `$key.GetValueNames() | ForEach-Object { Remove-ItemProperty -Path `$regPath -Name `$_ -ErrorAction SilentlyContinue } + Write-RestartLog "All registry values under `$regPath have been deleted" + } else { + Write-RestartLog "Registry key `$regPath not found, nothing to delete" + } + } elseif (`$LASTEXITCODE -eq 1190) { + Write-RestartLog "A system shutdown already exists: `$result" "Yellow" + + # Remove all values under the registry key + `$key = Get-Item -Path `$regPath -ErrorAction SilentlyContinue + if (`$key) { + `$key.GetValueNames() | ForEach-Object { Remove-ItemProperty -Path `$regPath -Name `$_ -ErrorAction SilentlyContinue } + Write-RestartLog "All registry values under `$regPath have been deleted" + } else { + Write-RestartLog "Registry key `$regPath not found, nothing to delete" + } } else { - Write-RestartLog "CcmRestart.exe not found, restarting ccmexec service" - Restart-Service ccmexec -Force -ErrorAction SilentlyContinue - Write-RestartLog "ccmexec service restart completed" + Write-RestartLog "A system shutdown failed: `$result" "Yellow" + + 'RebootBy','OverrideRebootWindowTime' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value ([Int64]-1) -PropertyType QWord -Force + } + 'PreferredRebootWindowTypes' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value @('3') -PropertyType MultiString -Force + } + 'OverrideRebootWindow','HardReboot','NotifyUI','RebootValueInUTC','SetTime','OverrideServiceWindows','RebootOutsideOfServiceWindow' | ForEach-Object { + New-ItemProperty -Path `$regPath -Name `$_ -Value 1 -PropertyType DWord -Force + } + New-ItemProperty -Path `$regPath -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force + + Write-RestartLog "Registry values updated for SCCM mandatory restart" + + # Check if CcmRestart.exe exists and use it, otherwise restart the service + if (Test-Path `$ccmRestartPath) { + Write-RestartLog "Executing CcmRestart.exe" + Start-Process -FilePath `$ccmRestartPath -NoNewWindow -Wait -ErrorAction SilentlyContinue + Write-RestartLog "CcmRestart.exe execution completed" + } else { + Write-RestartLog "CcmRestart.exe not found, restarting ccmexec service" + Restart-Service ccmexec -Force -ErrorAction SilentlyContinue + Write-RestartLog "ccmexec service restart completed" + } } } else { Write-RestartLog "No SCCM restart registry values found, task completed without action" From 24a00df55407a46a9f88eb7a9df5121d88c0a636 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sat, 28 Jun 2025 14:06:07 +0200 Subject: [PATCH 30/49] Final Hybrid Hard Reboot! --- .../functions/Test-WAUMods.ps1 | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 index 08685bb07..aaf346919 100644 --- a/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 +++ b/Sources/Winget-AutoUpdate/functions/Test-WAUMods.ps1 @@ -224,9 +224,12 @@ if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSO Write-RestartLog "SCCM restart registry values found, proceeding with restart" # Grace period is over, system will now restart in 2 minutes - Write-RestartLog "The grace period for restart (`$rebootDelay minutes) is over (`$shutdownMessage). System will restart in 2 minutes." "Yellow" + Write-RestartLog "Grace period: `$rebootDelay minutes is over (`$shutdownMessage). System will now restart in 2 minutes." "Yellow" - `$result = & shutdown /r /t 120 /c "Mandatory restart: The grace period for restart (`$rebootDelay minutes) is over (`$shutdownMessage). System will restart in 2 minutes." 2>&1 + # Cancels any pending system shutdown or restart using 'shutdown /a'. + `$null = & shutdown /a 2>&1 + + `$result = & shutdown /r /t 120 /c "Grace period: `$rebootDelay minutes is over (`$shutdownMessage). System will now restart in 2 minutes." 2>&1 if (`$LASTEXITCODE -eq 0) { Write-RestartLog "System restart scheduled in 2 minutes" "Yellow" @@ -282,20 +285,29 @@ if (`$regProps.PSObject.Properties.Name -contains 'RebootBy' -and `$regProps.PSO Write-RestartLog "Mandatory restart task completed" "@ - # Encode to Base64 $encodedScript = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptContent)) # Create action with encoded command $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -EncodedCommand $encodedScript" - $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($rebootDelay) + # Create a scheduled task trigger to run (rebootDelay - 2) minutes from now, but at least 2 minutes delay. + $triggerDelay = [math]::Max(($rebootDelay - 2), 2) + $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($triggerDelay) # Set EndBoundary to make DeleteExpiredTaskAfter work - $trigger.EndBoundary = (Get-Date).AddMinutes($rebootDelay).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") + $trigger.EndBoundary = (Get-Date).AddMinutes($triggerDelay).AddMinutes(1).ToString("yyyy-MM-ddTHH:mm:ss") $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -DeleteExpiredTaskAfter (New-TimeSpan -Seconds 0) $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Mandatory Restart by SCCM" -Force | Out-Null + Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Mandatory SCCM Restart" -Force | Out-Null + + # Add a standard shutdown command + $result = & shutdown /r /t ([int]($rebootDelay * 60)) /c $shutdownMessage 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-ToLog "System restart scheduled in $rebootDelay minutes" "Yellow" } else { + Write-ToLog "A system shutdown has already been scheduled or failed: $result" "Yellow" + } + } else { # SOFT/NON-MANDATORY REBOOT Write-ToLog "Using soft reboot (non-mandatory) for SCCM restart" "Cyan" From cf64d97905df9a5eeb72a71720790830ba5f565a Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 29 Jun 2025 13:44:44 +0200 Subject: [PATCH 31/49] Not stale --- Sources/Policies/ADMX/WAU.admx | 5 +++++ Sources/Policies/ADMX/en-US/WAU.adml | 10 ++++++---- .../Winget-AutoUpdate/functions/Start-NotifTask.ps1 | 2 +- Sources/Wix/build.wxs | 1 + 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Sources/Policies/ADMX/WAU.admx b/Sources/Policies/ADMX/WAU.admx index 3a4359aa1..56ed8c3c9 100644 --- a/Sources/Policies/ADMX/WAU.admx +++ b/Sources/Policies/ADMX/WAU.admx @@ -164,6 +164,11 @@ SuccessOnly + + + ErrorsOnly + + None diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index b29e47118..90f71a458 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -1,6 +1,6 @@ WinGet-AutoUpdate WinGet-AutoUpdate GPO Management @@ -58,7 +58,7 @@ (URL/UNC/GPO/Local) If this policy is enabled, you can set a (URL/UNC/GPO/Local) Path to external lists other than the default. - If "Application GPO Blacklist/Whitelist" is set in this GPO the Path can be: + If "Application GPO Blacklist/Whitelist" is set in this GPO the Path MUST be: GPO If this policy is disabled or not configured, the default ListPath is used @@ -84,12 +84,14 @@ configure the Notification Level: 1. Full (Default) 2. SuccessOnly - 3. None + 3. ErrorsOnly + 4. None If this policy is not configured or disabled, Notification Level: (1. Full). 1. Full (Default) 2. SuccessOnly - 3. None + 3. ErrorsOnly + 4. None Updates Interval If this policy is enabled, you can configure the Updates Interval: diff --git a/Sources/Winget-AutoUpdate/functions/Start-NotifTask.ps1 b/Sources/Winget-AutoUpdate/functions/Start-NotifTask.ps1 index 02c9d0469..6e8312ca3 100644 --- a/Sources/Winget-AutoUpdate/functions/Start-NotifTask.ps1 +++ b/Sources/Winget-AutoUpdate/functions/Start-NotifTask.ps1 @@ -15,7 +15,7 @@ function Start-NotifTask { [Switch]$UserRun = $false ) - if (($WAUConfig.WAU_NotificationLevel -eq "Full") -or ($WAUConfig.WAU_NotificationLevel -eq "SuccessOnly" -and $MessageType -eq "Success") -or ($UserRun)) { + if (($WAUConfig.WAU_NotificationLevel -eq "Full") -or ($WAUConfig.WAU_NotificationLevel -eq "SuccessOnly" -and $MessageType -eq "Success") -or ($WAUConfig.WAU_NotificationLevel -eq "ErrorsOnly" -and $MessageType -eq "Error") -or ($UserRun)) { # XML Toast template creation [xml]$ToastTemplate = New-Object system.Xml.XmlDocument diff --git a/Sources/Wix/build.wxs b/Sources/Wix/build.wxs index bd6ae3e8d..04a652f71 100644 --- a/Sources/Wix/build.wxs +++ b/Sources/Wix/build.wxs @@ -200,6 +200,7 @@ + From a93b567a96b8b4f822371af43cdf6b2f271ffa3a Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 29 Jun 2025 13:57:57 +0200 Subject: [PATCH 32/49] Update README.md to include 'Errors only' option in notification level descriptions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ee1451bf..918ef6d3e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ List and Mods folder content will be copied to WAU install location: ### Notification Level -You can choose which notification will be displayed: `Full`, `Success only` or `None`. +You can choose which notification will be displayed: `Full`, `Success only`, `Errors only` or `None`. ### Notification language You can easily translate toast notifications by creating your locale xml config file (and share it with us :) ). @@ -147,7 +147,7 @@ Set `DESKTOPSHORTCUT=1` to create a shortcut for user interaction on the Desktop Set `STARTMENUSHORTCUT=1` to create shortcuts for user interaction in the Start Menu to run task `Winget-AutoUpdate` and open Logs. ### NOTIFICATIONLEVEL -Specify the Notification level: Full (Default, displays all notification), SuccessOnly (Only displays notification for success) or None (Does not show any popup). +Specify the Notification level: Full (Default, displays all notification), SuccessOnly (Only displays notification for success), ErrorsOnly (Only displays notification for error) or None (Does not show any popup). ### UPDATESATLOGON Default value 1. Set `UPDATESATLOGON=0` to disable WAU from running at user logon. From e1ca1efcff2895f57bac66e064498de0abe8e183 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 29 Jun 2025 15:08:18 +0200 Subject: [PATCH 33/49] Plural --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 918ef6d3e..b987864cd 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Set `DESKTOPSHORTCUT=1` to create a shortcut for user interaction on the Desktop Set `STARTMENUSHORTCUT=1` to create shortcuts for user interaction in the Start Menu to run task `Winget-AutoUpdate` and open Logs. ### NOTIFICATIONLEVEL -Specify the Notification level: Full (Default, displays all notification), SuccessOnly (Only displays notification for success), ErrorsOnly (Only displays notification for error) or None (Does not show any popup). +Specify the Notification level: Full (Default, displays all notification), SuccessOnly (Only displays notification for success), ErrorsOnly (Only displays notification for errors) or None (Does not show any popup). ### UPDATESATLOGON Default value 1. Set `UPDATESATLOGON=0` to disable WAU from running at user logon. From dcfa95d887439481797384bd512402e9e4d131db Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 29 Jun 2025 16:26:20 +0200 Subject: [PATCH 34/49] Documentation --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1ee1451bf..9b4ed6cba 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,12 @@ Read more in the [Policies section](https://github.com/Romanitho/Winget-AutoUpda This script executes **if the network is active/any version of Winget is installed/WAU is running as SYSTEM**.
If **ExitCode** is **1** from `_WAU-mods.ps1` then **Re-run WAU**. +In addition to this legacy handling, **function {Test-WAUMods}** now supports a new action-based system. + +This allows you to define multiple actions and conditions directly in your mod script, enabling more advanced automation scenarios. + +With actions, you can execute different scripts, check results, and control the WAU flow with greater flexibility and improved logging compared to relying solely on **Exit Code**. + Likewise `_WAU-mods-postsys.ps1` can be used to do things at the end of the **SYSTEM context WAU** process before the user run. ## Custom scripts (Mods feature for Apps) From cc0f89a3282584757a9d1e06c78719306b6718bd Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Sun, 29 Jun 2025 18:45:58 +0200 Subject: [PATCH 35/49] Nobody cares about functions... --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9b4ed6cba..353d0bb56 100644 --- a/README.md +++ b/README.md @@ -189,10 +189,8 @@ Read more in the [Policies section](https://github.com/Romanitho/Winget-AutoUpda This script executes **if the network is active/any version of Winget is installed/WAU is running as SYSTEM**.
If **ExitCode** is **1** from `_WAU-mods.ps1` then **Re-run WAU**. -In addition to this legacy handling, **function {Test-WAUMods}** now supports a new action-based system. - -This allows you to define multiple actions and conditions directly in your mod script, enabling more advanced automation scenarios. - +In addition to this legacy handling, a new action-based system is now supported.
+This system lets you define multiple actions and conditions directly in your mod scripts, enabling more advanced automation and control over the WAU process.
With actions, you can execute different scripts, check results, and control the WAU flow with greater flexibility and improved logging compared to relying solely on **Exit Code**. Likewise `_WAU-mods-postsys.ps1` can be used to do things at the end of the **SYSTEM context WAU** process before the user run. From 7f169585a964c669b8908127306e75e29953a349 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Mon, 30 Jun 2025 05:29:11 +0200 Subject: [PATCH 36/49] Crash with revision 5.0. Fix a string id row. --- Sources/Policies/ADMX/en-US/WAU.adml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index 90f71a458..d78684a75 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -1,6 +1,6 @@ WinGet-AutoUpdate WinGet-AutoUpdate GPO Management @@ -54,8 +54,7 @@ Whitelist or not. If this policy is disabled or not configured, the default is No.
- Get Black/White List from external Path - (URL/UNC/GPO/Local) + Get Black/White List from external Path (URL/UNC/GPO/Local) If this policy is enabled, you can set a (URL/UNC/GPO/Local) Path to external lists other than the default. If "Application GPO Blacklist/Whitelist" is set in this GPO the Path MUST be: From 0d7c2c04b4baec659c92fbc780fe3e00c0eb1010 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Mon, 30 Jun 2025 07:25:36 +0200 Subject: [PATCH 37/49] revision="4.9" works, next time set in ADMX resources minRequiredRevision="5.0" and increase to 5.0 in ADML! --- Sources/Policies/ADMX/en-US/WAU.adml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index d78684a75..c53b59033 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -1,6 +1,6 @@ WinGet-AutoUpdate WinGet-AutoUpdate GPO Management From 2cd9fbe44bd5baf0714b6792013bff0eef182f36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 08:12:42 +0000 Subject: [PATCH 38/49] Bump ncipollo/release-action from 1.16.0 to 1.18.0 Bumps [ncipollo/release-action](https://github.com/ncipollo/release-action) from 1.16.0 to 1.18.0. - [Release notes](https://github.com/ncipollo/release-action/releases) - [Commits](https://github.com/ncipollo/release-action/compare/440c8c1cb0ed28b9f43e4d1d670870f059653174...bcfe5470707e8832e12347755757cec0eb3c22af) --- updated-dependencies: - dependency-name: ncipollo/release-action dependency-version: 1.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml | 2 +- .github/workflows/GitFlow_Nightly-builds.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml b/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml index c5dec4a2e..d231311ac 100644 --- a/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml +++ b/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml @@ -112,7 +112,7 @@ jobs: # Step 4: Create stable GitHub release with all artifacts - name: Create release - uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0 + uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0 with: tag: v${{ steps.release_version.outputs.NextSemVer }} prerelease: false # This is a stable release diff --git a/.github/workflows/GitFlow_Nightly-builds.yml b/.github/workflows/GitFlow_Nightly-builds.yml index c919ad26c..cdfa00177 100644 --- a/.github/workflows/GitFlow_Nightly-builds.yml +++ b/.github/workflows/GitFlow_Nightly-builds.yml @@ -155,7 +155,7 @@ jobs: # Step 6: Create GitHub release with all artifacts - name: Create release - uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0 + uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0 if: steps.check_prs.outputs.BUILD_NEEDED == 'true' with: tag: v${{ steps.format_version.outputs.NextSemVer }} From 909ce39acdff1e9c50b1d6d43062221d1ef9c380 Mon Sep 17 00:00:00 2001 From: Fabian Seitz Date: Thu, 10 Jul 2025 11:22:18 +0200 Subject: [PATCH 39/49] fix cmd not found error --- Sources/Winget-AutoUpdate/Winget-Install.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/Winget-AutoUpdate/Winget-Install.ps1 b/Sources/Winget-AutoUpdate/Winget-Install.ps1 index 84d34324b..81af1d534 100644 --- a/Sources/Winget-AutoUpdate/Winget-Install.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Install.ps1 @@ -309,9 +309,18 @@ if ("$env:PROCESSOR_ARCHITEW6432" -ne "ARM64") { } } -#Config console output encoding -$null = cmd /c '' #Tip for ISE +# Workaround for ISE: Force UTF-8 output encoding by briefly invoking cmd.exe +if ($psISE) { + try { + $null = Start-Process "cmd.exe" -ArgumentList "/c """ -NoNewWindow -Wait -WindowStyle Hidden + } + catch { + Write-ToLog "-> Unable to execute cmd.exe - skipping ISE output encoding workaround." "Red" + } +} +# Set UTF-8 encoding for all console output (e.g., Write-Output, Write-Host, etc.) [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +# Suppress progress bars (used by some cmdlets like Invoke-WebRequest) $Script:ProgressPreference = 'SilentlyContinue' #Check if current process is elevated (System or admin user) From 23f1901322c939c35f41ce3580a37173ed7fe787 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:03:04 +0200 Subject: [PATCH 40/49] Change folder and add header --- .../config => Tools/Detection}/winget-detect.ps1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) rename Sources/{Winget-AutoUpdate/config => Tools/Detection}/winget-detect.ps1 (84%) diff --git a/Sources/Winget-AutoUpdate/config/winget-detect.ps1 b/Sources/Tools/Detection/winget-detect.ps1 similarity index 84% rename from Sources/Winget-AutoUpdate/config/winget-detect.ps1 rename to Sources/Tools/Detection/winget-detect.ps1 index ed4d0ade4..6c83d7d07 100644 --- a/Sources/Winget-AutoUpdate/config/winget-detect.ps1 +++ b/Sources/Tools/Detection/winget-detect.ps1 @@ -1,3 +1,12 @@ +<# +.SYNOPSIS +Helper script to use as detection method with Intune or SCCM. + +.DESCRIPTION +This script uses `winget export` to detect if a specific application is installed. +Intended for use as a detection rule script in Intune or SCCM deployments. +#> + #Change app to detect [Application ID] $AppToDetect = "Notepad++.Notepad++" @@ -50,4 +59,4 @@ $Apps = $Packages | Where-Object { $_.PackageIdentifier -eq $AppToDetect } if ($Apps) { return "Installed!" -} \ No newline at end of file +} From 252f0db12f53280949edbb81043533e15d400210 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:19:25 +0200 Subject: [PATCH 41/49] Update README.md too --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2efbb0418..a65046bb3 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ Instead of including the override parameters in the install string you can use a * A standard single installation: **-AppIDs Notepad++.Notepad++** * Multiple installations: **-AppIDs "7zip.7zip, Notepad++.Notepad++"** -As a detection script use **config\winget-detect.ps1** (change app to detect [**Application ID**]) in **Intune**/**SCCM** ([winget-detect.ps1](Sources/Winget-AutoUpdate/config/winget-detect.ps1)) +As a detection script use **config\winget-detect.ps1** (change app to detect [**Application ID**]) in **Intune**/**SCCM** ([winget-detect.ps1](Sources/Winget-AutoUpdate/Tools/Detection/winget-detect.ps1)) A nice feature is if you're already using the deprecated standalone script **winget-install.ps1** from the [old repo](https://github.com/Romanitho/Winget-Install) and have placed it somwhere locally on all clients you can make a **SymLink** in its place and keep using the old path (avoiding a lot of work) in your deployed applications (**Winget-Install.ps1** takes care of the SymLink logic). From 99e315994f87d2fbed230b903e80ea49704e0c62 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Fri, 18 Jul 2025 15:15:01 +0200 Subject: [PATCH 42/49] Small fix on merging with comment --- .github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml b/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml index d87f7070b..4cace8b11 100644 --- a/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml +++ b/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml @@ -24,6 +24,8 @@ jobs: # Run only when PR is merged (not just closed) and source branch is release/* or hotfix/* if: github.event.pull_request.merged == true && (startsWith(github.event.pull_request.head.ref, 'release/') || startsWith(github.event.pull_request.head.ref, 'hotfix/')) runs-on: windows-latest + outputs: + next_semver: ${{ steps.release_version.outputs.NextSemVer }} steps: # Step 1: Checkout the code from the main branch after merge - name: Checkout code @@ -137,7 +139,7 @@ jobs: uses: actions/checkout@v4.2.2 with: fetch-depth: 0 - + # Step 5: Configure Git for merge back to develop - name: Configure Git shell: bash @@ -152,9 +154,8 @@ jobs: # Checkout develop branch git checkout develop git pull origin develop - # Merge main into develop with no fast-forward to preserve history - git merge --no-ff origin/main -m "Merge main into develop after the creation of release v${{ steps.release_version.outputs.NextSemVer }}" + git merge --no-ff origin/main -m "Merge main into develop after the creation of release v${{ needs.build.outputs.next_semver }}" # Push changes to develop branch git push origin develop From e405432967b852a4199521502781e3ffb8312a43 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:07:51 +0200 Subject: [PATCH 43/49] Deprecate ActivateGPOManagement Setting --- Sources/Policies/ADMX/en-US/WAU.adml | 27 +++++++++------ Sources/Winget-AutoUpdate/WAU-Policies.ps1 | 33 ++++++------------- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 19 +++-------- .../functions/Get-WAUConfig.ps1 | 13 +++----- 4 files changed, 36 insertions(+), 56 deletions(-) diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index c53b59033..51bbf1978 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -13,9 +13,13 @@ Winget-AutoUpdate version 2.6.0 or later Winget-AutoUpdate Experimental, subject to change, do not use on PROD - Activate WAU GPO Management - This policy setting is an overriding - toggle for GPO Management of Winget-AutoUpdate. + Activate WAU GPO Management [DEPRECATED] + This policy setting is deprecated and will be + removed in a future version. The GPO Management is now always enabled when GPO policies are + configured. + + This policy previously controlled whether GPO Management of Winget-AutoUpdate was active or + not. Bypass Black/White list for User This policy setting specifies whether to Bypass Black/White list when run in user context or not. @@ -188,14 +192,17 @@ If this policy is disabled or not configured, the default is always the built-in winget. Random delay for scheduled task triggers - This policy setting specifies the delay for the scheduled task. - A scheduled task random delay adds a random amount of wait time (up to the specified maximum) before the task starts. - This helps prevent many devices from running the task at the exact same time. This is not applicable to "on logon" triggers. + This policy setting specifies the delay for the + scheduled task. + A scheduled task random delay adds a random amount of wait time (up to the specified + maximum) before the task starts. + This helps prevent many devices from running the task at the exact same time. This is not + applicable to "on logon" triggers. - If this policy is enabled, the scheduled task will have a random delay set - based on the time inputted. + If this policy is enabled, the scheduled task will have a random delay set + based on the time inputted. - If this policy is disabled or not configured, the default no delay. + If this policy is disabled or not configured, the default no delay. @@ -250,4 +257,4 @@ - +
\ No newline at end of file diff --git a/Sources/Winget-AutoUpdate/WAU-Policies.ps1 b/Sources/Winget-AutoUpdate/WAU-Policies.ps1 index fb12725c3..af5e17d3a 100644 --- a/Sources/Winget-AutoUpdate/WAU-Policies.ps1 +++ b/Sources/Winget-AutoUpdate/WAU-Policies.ps1 @@ -9,18 +9,13 @@ Daily update settings from policies #Import functions . "$PSScriptRoot\functions\Get-WAUConfig.ps1" -#Check if GPO Management is enabled -$ActivateGPOManagement = Get-ItemPropertyValue "HKLM:\SOFTWARE\Policies\Romanitho\Winget-AutoUpdate" -Name "WAU_ActivateGPOManagement" -ErrorAction SilentlyContinue -if ($ActivateGPOManagement -eq 1) { - #Add (or update) tag to activate WAU-Policies Management - New-ItemProperty "HKLM:\SOFTWARE\Romanitho\Winget-AutoUpdate" -Name WAU_RunGPOManagement -Value 1 -Force | Out-Null -} +#Check if GPO Management is detected +$GPOManagementDetected = Get-ItemProperty "HKLM:\SOFTWARE\Policies\Romanitho\Winget-AutoUpdate" -ErrorAction SilentlyContinue -#Get WAU settings -$WAUConfig = Get-WAUConfig +if ($GPOManagementDetected) { -#Check if GPO got applied from Get-WAUConfig (tag) -if ($WAUConfig.WAU_RunGPOManagement -eq 1) { + #Get WAU settings + $WAUConfig = Get-WAUConfig #Log init $GPOLogDirectory = Join-Path -Path $WAUConfig.InstallLocation -ChildPath "logs" @@ -30,16 +25,6 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { $GPOLogFile = Join-Path -Path $GPOLogDirectory -ChildPath "LatestAppliedSettings.txt" Set-Content -Path $GPOLogFile -Value "### POLICY CYCLE - $(Get-Date) ###`n" - #Reset WAU_RunGPOManagement if not GPO managed anymore (This is used to run this job one last time and reset initial settings) - if ($($WAUConfig.WAU_ActivateGPOManagement -eq 1)) { - Add-Content -Path $GPOLogFile -Value "GPO Management Enabled. Policies updated." - } - else { - New-ItemProperty "HKLM:\SOFTWARE\Romanitho\Winget-AutoUpdate" -Name WAU_RunGPOManagement -Value 0 -Force | Out-Null - $WAUConfig.WAU_RunGPOManagement = 0 - Add-Content -Path $GPOLogFile -Value "GPO Management Disabled. Policies removed." - } - #Get Winget-AutoUpdate scheduled task $WAUTask = Get-ScheduledTask -TaskName 'Winget-AutoUpdate' -ErrorAction SilentlyContinue @@ -49,7 +34,7 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { #Check if LogOn trigger setting has changed $hasLogonTrigger = $currentTriggers | Where-Object { $_.CimClass.CimClassName -eq "MSFT_TaskLogonTrigger" } - if (($WAUConfig.WAU_UpdatesAtLogon -eq 1 -and -not $hasLogonTrigger) -or + if (($WAUConfig.WAU_UpdatesAtLogon -eq 1 -and -not $hasLogonTrigger) -or ($WAUConfig.WAU_UpdatesAtLogon -ne 1 -and $hasLogonTrigger)) { $configChanged = $true } @@ -98,6 +83,7 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { if ($existingRandomDelay -ne $randomDelay) { $configChanged = $true } + #Check if schedule time has changed if ($currentIntervalType -ne "None" -and $currentIntervalType -ne "Never") { if ($timeTrigger) { @@ -129,7 +115,7 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { elseif ($WAUConfig.WAU_UpdatesInterval -eq "Monthly") { $tasktriggers += New-ScheduledTaskTrigger -Weekly -At $WAUConfig.WAU_UpdatesAtTime -DaysOfWeek 2 -WeeksInterval 4 -RandomDelay $randomDelay } - + #If trigger(s) set if ($taskTriggers) { #Edit scheduled task @@ -142,10 +128,11 @@ if ($WAUConfig.WAU_RunGPOManagement -eq 1) { Set-ScheduledTask -TaskPath $WAUTask.TaskPath -TaskName $WAUTask.TaskName -Trigger $tasktriggers | Out-Null } } - + #Log latest applied config Add-Content -Path $GPOLogFile -Value "`nLatest applied settings:" $WAUConfig.PSObject.Properties | Where-Object { $_.Name -like "WAU_*" } | Select-Object Name, Value | Out-File -Encoding default -FilePath $GPOLogFile -Append + } Exit 0 diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index 998aa9013..a70f3fe33 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -19,15 +19,6 @@ $Script:ProgressPreference = [System.Management.Automation.ActionPreference]::Si # Log initialization [string]$LogFile = [System.IO.Path]::Combine($Script:WorkingDir, 'logs', 'updates.log'); -#region Get settings and Domain/Local Policies (GPO) if activated. -Write-ToLog "Reading WAUConfig"; -$Script:WAUConfig = Get-WAUConfig; - -if ($WAUConfig.WAU_ActivateGPOManagement -eq 1) { - Write-ToLog "WAU Policies management Enabled."; -} -#endregion Get settings and Domain/Local Policies (GPO) if activated. - # Default name of winget repository used within this script [string]$DefaultWingetRepoName = 'winget'; @@ -35,12 +26,10 @@ if ($WAUConfig.WAU_ActivateGPOManagement -eq 1) { # Defining a custom source even if not used below (failsafe suggested by github/sebneus mentioned in issues/823) [string]$Script:WingetSourceCustom = $DefaultWingetRepoName; -# Defining custom repository for winget tool (only if GPO management is active) -if ($Script:WAUConfig.WAU_ActivateGPOManagement) { - if ($null -ne $Script:WAUConfig.WAU_WingetSourceCustom) { - $Script:WingetSourceCustom = $Script:WAUConfig.WAU_WingetSourceCustom.Trim(); - Write-ToLog "Selecting winget repository named '$($Script:WingetSourceCustom)'"; - } +# Defining custom repository for winget tool +if ($null -ne $Script:WAUConfig.WAU_WingetSourceCustom) { + $Script:WingetSourceCustom = $Script:WAUConfig.WAU_WingetSourceCustom.Trim(); + Write-ToLog "Selecting winget repository named '$($Script:WingetSourceCustom)'"; } #endregion Winget Source Custom diff --git a/Sources/Winget-AutoUpdate/functions/Get-WAUConfig.ps1 b/Sources/Winget-AutoUpdate/functions/Get-WAUConfig.ps1 index a202d55be..8481d223d 100644 --- a/Sources/Winget-AutoUpdate/functions/Get-WAUConfig.ps1 +++ b/Sources/Winget-AutoUpdate/functions/Get-WAUConfig.ps1 @@ -6,20 +6,17 @@ Function Get-WAUConfig { $WAUConfig_64_86 = Get-ItemProperty -Path "HKLM:\SOFTWARE\Romanitho\Winget-AutoUpdate*", "HKLM:\SOFTWARE\WOW6432Node\Romanitho\Winget-AutoUpdate*" -ErrorAction SilentlyContinue | Sort-Object { $_.ProductVersion } -Descending $WAUConfig = $WAUConfig_64_86[0] - #Check if GPO Management is enabled - $ActivateGPOManagement = Get-ItemPropertyValue "HKLM:\SOFTWARE\Policies\Romanitho\Winget-AutoUpdate" -Name "WAU_ActivateGPOManagement" -ErrorAction SilentlyContinue + #Check if GPO policies exist + $WAUPolicies = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Romanitho\Winget-AutoUpdate" -ErrorAction SilentlyContinue - #If GPO Management is enabled, replace settings - if ($ActivateGPOManagement -eq 1) { - - #Get all WAU Policies - $WAUPolicies = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Romanitho\Winget-AutoUpdate" -ErrorAction SilentlyContinue + #If GPO policies exist, apply them (regardless of ActivateGPOManagement value) + if ($WAUPolicies) { + Write-ToLog "GPO policies detected - applying GPO configuration" "Yellow" #Replace loaded configurations by ones from Policies $WAUPolicies.PSObject.Properties | ForEach-Object { $WAUConfig.PSObject.Properties.add($_) } - } #Return config From 19d4d27bd045396da6602afe09d573fb66352e04 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Fri, 18 Jul 2025 18:11:25 +0200 Subject: [PATCH 44/49] Docu Detection Script --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9708498cf..7f23dfe51 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Instead of including the override parameters in the install string you can use a * A standard single installation: **-AppIDs Notepad++.Notepad++** * Multiple installations: **-AppIDs "7zip.7zip, Notepad++.Notepad++"** -As a detection script use **config\winget-detect.ps1** (change app to detect [**Application ID**]) in **Intune**/**SCCM** ([winget-detect.ps1](Sources/Winget-AutoUpdate/Tools/Detection/winget-detect.ps1)) +As a detection script you can download/edit **winget-detect.ps1** (change app to detect [**Application ID**]) in **Intune**/**SCCM** ([winget-detect.ps1](Sources/Winget-AutoUpdate/Tools/Detection/winget-detect.ps1)) A nice feature is if you're already using the deprecated standalone script **winget-install.ps1** from the [old repo](https://github.com/Romanitho/Winget-Install) and have placed it somwhere locally on all clients you can make a **SymLink** in its place and keep using the old path (avoiding a lot of work) in your deployed applications (**Winget-Install.ps1** takes care of the SymLink logic). From 07680a5378fb0f94226eb68729a602a38c3f8e10 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Fri, 18 Jul 2025 18:13:13 +0200 Subject: [PATCH 45/49] Link in text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f23dfe51..3355f231a 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Instead of including the override parameters in the install string you can use a * A standard single installation: **-AppIDs Notepad++.Notepad++** * Multiple installations: **-AppIDs "7zip.7zip, Notepad++.Notepad++"** -As a detection script you can download/edit **winget-detect.ps1** (change app to detect [**Application ID**]) in **Intune**/**SCCM** ([winget-detect.ps1](Sources/Winget-AutoUpdate/Tools/Detection/winget-detect.ps1)) +As a custom detection script you can download/edit [winget-detect.ps1](Sources/Winget-AutoUpdate/Tools/Detection/winget-detect.ps1) (change app to detect [**Application ID**]) in **Intune**/**SCCM** A nice feature is if you're already using the deprecated standalone script **winget-install.ps1** from the [old repo](https://github.com/Romanitho/Winget-Install) and have placed it somwhere locally on all clients you can make a **SymLink** in its place and keep using the old path (avoiding a lot of work) in your deployed applications (**Winget-Install.ps1** takes care of the SymLink logic). From 2eab278644994e923abd16a6a7b93d5513d8f8a5 Mon Sep 17 00:00:00 2001 From: KnifMelti Date: Fri, 18 Jul 2025 18:22:45 +0200 Subject: [PATCH 46/49] Path error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3355f231a..5a644ba53 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Instead of including the override parameters in the install string you can use a * A standard single installation: **-AppIDs Notepad++.Notepad++** * Multiple installations: **-AppIDs "7zip.7zip, Notepad++.Notepad++"** -As a custom detection script you can download/edit [winget-detect.ps1](Sources/Winget-AutoUpdate/Tools/Detection/winget-detect.ps1) (change app to detect [**Application ID**]) in **Intune**/**SCCM** +As a custom detection script you can download/edit [winget-detect.ps1](Sources/Tools/Detection/winget-detect.ps1) (change app to detect [**Application ID**]) in **Intune**/**SCCM** A nice feature is if you're already using the deprecated standalone script **winget-install.ps1** from the [old repo](https://github.com/Romanitho/Winget-Install) and have placed it somwhere locally on all clients you can make a **SymLink** in its place and keep using the old path (avoiding a lot of work) in your deployed applications (**Winget-Install.ps1** takes care of the SymLink logic). From 5c0fba16ff43b7eeb132496d0d68cd5789589b46 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:42:39 +0200 Subject: [PATCH 47/49] cleaning old deprecated --- Sources/Policies/ADMX/WAU.admx | 25 ---- Sources/Policies/ADMX/en-US/WAU.adml | 184 ++++++++++----------------- 2 files changed, 67 insertions(+), 142 deletions(-) diff --git a/Sources/Policies/ADMX/WAU.admx b/Sources/Policies/ADMX/WAU.admx index 56ed8c3c9..0f76dddc5 100644 --- a/Sources/Policies/ADMX/WAU.admx +++ b/Sources/Policies/ADMX/WAU.admx @@ -372,31 +372,6 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/Policies/ADMX/en-US/WAU.adml b/Sources/Policies/ADMX/en-US/WAU.adml index 51bbf1978..3403823a4 100644 --- a/Sources/Policies/ADMX/en-US/WAU.adml +++ b/Sources/Policies/ADMX/en-US/WAU.adml @@ -11,119 +11,94 @@ Winget-AutoUpdate version 1.16.0 or later Winget-AutoUpdate version 1.16.5 or later Winget-AutoUpdate version 2.6.0 or later - Winget-AutoUpdate Experimental, subject to change, do - not use on PROD + Winget-AutoUpdate Experimental, subject to change, do not use on PROD Activate WAU GPO Management [DEPRECATED] - This policy setting is deprecated and will be - removed in a future version. The GPO Management is now always enabled when GPO policies are - configured. + This policy setting is deprecated and will be removed in a future version. The GPO Management is now always enabled when GPO policies are configured. - This policy previously controlled whether GPO Management of Winget-AutoUpdate was active or - not. +This policy previously controlled whether GPO Management of Winget-AutoUpdate was active or not. Bypass Black/White list for User - This policy setting specifies whether to - Bypass Black/White list when run in user context or not. + This policy setting specifies whether to Bypass Black/White list when run in user context or not. - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Disable WAU AutoUpdate - This policy setting specifies whether to - Disable WAU AutoUpdate or not: - By default, WAU AutoUpdate is enabled. - It will not overwrite the configurations, icons (if personalised), - excluded_apps list... + This policy setting specifies whether to Disable WAU AutoUpdate or not: +By default, WAU AutoUpdate is enabled. +It will not overwrite the configurations, icons (if personalised), +excluded_apps list... - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Run WAU on metered connection - This policy setting specifies whether to - Run WAU on metered connection or not. + This policy setting specifies whether to Run WAU on metered connection or not. - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Update WAU to PreRelease versions - This policy setting specifies whether to - update WAU to PreRelease versions or not (via WAU AutoUpdate). + This policy setting specifies whether to update WAU to PreRelease versions or not (via WAU AutoUpdate). - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Application GPO Blacklist - Provide the WinGet IDs of applications you want to - exclude. + Provide the WinGet IDs of applications you want to exclude. - If this policy is disabled or not configured, GPO Blacklist is not used. +If this policy is disabled or not configured, GPO Blacklist is not used. Application GPO Whitelist - Provide the WinGet IDs of applications you want to - include. + Provide the WinGet IDs of applications you want to include. - If this policy is disabled or not configured, GPO Whitelist is not used. +If this policy is disabled or not configured, GPO Whitelist is not used. Use WhiteList instead of BlackList - This policy setting specifies whether to use a - Whitelist or not. + This policy setting specifies whether to use a Whitelist or not. - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Get Black/White List from external Path (URL/UNC/GPO/Local) - If this policy is enabled, you can set a - (URL/UNC/GPO/Local) Path to external lists other than the default. - If "Application GPO Blacklist/Whitelist" is set in this GPO the Path MUST be: - GPO + If this policy is enabled, you can set a (URL/UNC/GPO/Local) Path to external lists other than the default. +If "Application GPO Blacklist/Whitelist" is set in this GPO the Path MUST be: GPO - If this policy is disabled or not configured, the default ListPath is used - (WAU InstallLocation). +If this policy is disabled or not configured, the default ListPath is used (WAU InstallLocation). Get Mods from external Path (URL/UNC/Local/AzureBlob) - If this policy is enabled, you can set a - (URL/UNC/Local/AzureBlob) Path to external mods other than the default. + If this policy is enabled, you can set a (URL/UNC/Local/AzureBlob) Path to external mods other than the default. - If this policy is disabled or not configured, the default ModsPath is used - (WAU InstallLocation). +If this policy is disabled or not configured, the default ModsPath is used (WAU InstallLocation). - Note: When set to 'AzureBlob', ensure you also configure 'Set Azure Blob URL - with SAS token'. +Note: When set to 'AzureBlob', ensure you also configure 'Set Azure Blob URL with SAS token'. Set Azure Blob URL with SAS Token - If this policy is enabled, you can set an Azure - Storage Blob URL with SAS token for use with the 'Mods' feature. The URL - must include the SAS token and have 'read' and 'list' permissions. + If this policy is enabled, you can set an Azure Storage Blob URL with SAS token for use with the 'Mods' feature. The URL must include the SAS token and have 'read' and 'list' permissions. - If this policy is disabled or not configured, the value is blank and Azure - Blob storage will NOT work. +If this policy is disabled or not configured, the value is blank and Azure Blob storage will NOT work. Notification Level - If this policy is enabled, you can - configure the Notification Level: - 1. Full (Default) - 2. SuccessOnly - 3. ErrorsOnly - 4. None + If this policy is enabled, you can configure the Notification Level: + 1. Full (Default) + 2. SuccessOnly + 3. ErrorsOnly + 4. None - If this policy is not configured or disabled, Notification Level: (1. Full). +If this policy is not configured or disabled, Notification Level: (1. Full). 1. Full (Default) 2. SuccessOnly 3. ErrorsOnly 4. None Updates Interval - If this policy is enabled, you can configure - the Updates Interval: - 1. Daily (Default) - 2. BiDaily - 3. Weekly - 4. BiWeekly - 5. Monthly - 6. Never (e.g. in combination with 'Updates at Logon') - - If this policy is not configured or disabled, Updates Interval: (1. Daily). + If this policy is enabled, you can configure the Updates Interval: + 1. Daily (Default) + 2. BiDaily + 3. Weekly + 4. BiWeekly + 5. Monthly + 6. Never (e.g. in combination with 'Updates at Logon') + +If this policy is not configured or disabled, Updates Interval: (1. Daily). 1. Daily (Default) 2. BiDaily 3. Weekly 4. BiWeekly 5. Monthly - 6. Never (e.g. in combination with 'Updates - Interval') + 6. Never (e.g. in combination with 'Updates Interval') Updates at Logon - This policy setting specifies whether to set - WAU to run at user logon or not. + This policy setting specifies whether to set WAU to run at user logon or not. - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Updates at Time - If this policy is enabled, you can configure - the Sheduled Task Update time: - From 01:00 to 24:00 (Military/24 Hour Time) + If this policy is enabled, you can configure the Sheduled Task Update time: + From 01:00 to 24:00 (Military/24 Hour Time) - If this policy is not configured or disabled, Updates at Time: (06:00 AM). +If this policy is not configured or disabled, Updates at Time: (06:00 AM). 01:00 AM 02:00 03:00 @@ -149,60 +124,35 @@ 23:00 24:00 User context execution - This policy setting specifies whether to enable - User context execution or not. - - If this policy is disabled or not configured, the default is No. - Enable Deskop Shortcut [DEPRECATED] - This policy setting specifies whether to - enable a Desktop Shortcut or not: - WAU - Check for updated Apps - - If this policy is disabled or not configured, the default is No. - Enable Start Menu Shortcuts [DEPRECATED] - This policy setting specifies whether to - enable the Start Menu Shortcuts or not: - WAU - Check for updated Apps - WAU - Open logs - WAU - Web Help + This policy setting specifies whether to enable User context execution or not. - If this policy is disabled or not configured, the default is No. +If this policy is disabled or not configured, the default is No. Log: Number of allowed log files - If this policy is enabled, you can set a number - of allowed log files: - Setting MaxLogFiles to 0 don't delete any old archived log files, 1 keeps - the original one and just let it grow. - Default number is 3 (0-99) + If this policy is enabled, you can set a number of allowed log files: +Setting MaxLogFiles to 0 don't delete any old archived log files, 1 keeps the original one and just let it grow. +Default number is 3 (0-99) - If this policy is disabled or not configured, the default number is used. +If this policy is disabled or not configured, the default number is used. Log: Size of the log file in bytes before rotating - If this policy is enabled, you can set the size of - the log file in bytes before rotating. - Default size is 1048576 = 1 MB + If this policy is enabled, you can set the size of the log file in bytes before rotating. +Default size is 1048576 = 1 MB - If this policy is disabled or not configured, the default size is used. +If this policy is disabled or not configured, the default size is used. Use custom Winget Source repository - This policy setting specifies whether to - use winget tool with custom repository or not: - (WAU - Check for updated Apps) + This policy setting specifies whether to use winget tool with custom repository or not: +(WAU - Check for updated Apps) - If this policy is enabled, WAU will TRY to use a custom repo instead of the - built-in/default one called winget. +If this policy is enabled, WAU will TRY to use a custom repo instead of the built-in/default one called winget. - If this policy is disabled or not configured, the default is always the - built-in winget. +If this policy is disabled or not configured, the default is always the built-in winget. Random delay for scheduled task triggers - This policy setting specifies the delay for the - scheduled task. - A scheduled task random delay adds a random amount of wait time (up to the specified - maximum) before the task starts. - This helps prevent many devices from running the task at the exact same time. This is not - applicable to "on logon" triggers. + This policy setting specifies the delay for the scheduled task. +A scheduled task random delay adds a random amount of wait time (up to the specified maximum) before the task starts. +This helps prevent many devices from running the task at the exact same time. This is not applicable to "on logon" triggers. - If this policy is enabled, the scheduled task will have a random delay set - based on the time inputted. +If this policy is enabled, the scheduled task will have a random delay set based on the time inputted. - If this policy is disabled or not configured, the default no delay. +If this policy is disabled or not configured, the default no delay. From 8c1d5cd97cd0461cee34c9fd9881bc46b509eef5 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Sat, 19 Jul 2025 11:02:14 +0200 Subject: [PATCH 48/49] Restore Get-WAUConfig after accidental removal --- .github/workflows/GitFlow_Nightly-builds.yml | 16 +++++++++++++--- Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GitFlow_Nightly-builds.yml b/.github/workflows/GitFlow_Nightly-builds.yml index 571c52441..3276141b8 100644 --- a/.github/workflows/GitFlow_Nightly-builds.yml +++ b/.github/workflows/GitFlow_Nightly-builds.yml @@ -10,6 +10,8 @@ on: # Automated nightly builds at midnight schedule: - cron: "0 0 * * *" + # Manual trigger for testing purposes + workflow_dispatch: permissions: contents: write @@ -91,16 +93,24 @@ jobs: $MsiBase = $NextSemver.Split("-")[0] # Remove prerelease segment $MsiVersion = "$MsiBase.$commit_count_mod" - # Format the release name - $ReleaseName = "WAU $NextSemver [Nightly Build]" + # Format the release name based on trigger type + if ("${{ github.event_name }}" -eq "workflow_dispatch") { + $ReleaseName = "WAU $NextSemver [Pre-release Build]" + $ReleaseBodyIntro = "This is a **pre-release build** created from the latest changes in the develop branch." + } else { + $ReleaseName = "WAU $NextSemver [Nightly Build]" + $ReleaseBodyIntro = "This is an **automated nightly build** created from the latest changes in the develop branch." + } # Output all version information echo "MSI version: $MsiVersion" echo "Semver created: $NextSemver" echo "Release name: $ReleaseName" + echo "Release body intro: $ReleaseBodyIntro" echo "MsiVersion=$MsiVersion" >> $env:GITHUB_OUTPUT echo "NextSemVer=$NextSemver" >> $env:GITHUB_OUTPUT echo "ReleaseName=$ReleaseName" >> $env:GITHUB_OUTPUT + echo "ReleaseBodyIntro=$ReleaseBodyIntro" >> $env:GITHUB_OUTPUT # Step 5: Build the project and generate artifacts - name: Build project @@ -165,7 +175,7 @@ jobs: name: ${{ steps.format_version.outputs.ReleaseName }} artifacts: "WAU.msi,WAU_ADMX.zip,WAU_InstallCounter" body: | - This is an **automated nightly build** created from the latest changes in the develop branch. + ${{ steps.format_version.outputs.ReleaseBodyIntro }} ⚠️ **Warning**: This build may contain unstable features and is intended for testing purposes only. diff --git a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 index a70f3fe33..935f9ce5d 100644 --- a/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Sources/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -19,6 +19,11 @@ $Script:ProgressPreference = [System.Management.Automation.ActionPreference]::Si # Log initialization [string]$LogFile = [System.IO.Path]::Combine($Script:WorkingDir, 'logs', 'updates.log'); +#region Get settings and Domain/Local Policies (GPO) if activated. +Write-ToLog "Reading WAUConfig"; +$Script:WAUConfig = Get-WAUConfig; +#endregion Get settings and Domain/Local Policies (GPO) if activated. + # Default name of winget repository used within this script [string]$DefaultWingetRepoName = 'winget'; From 0a1448f4d82adaf478eb06c81149212e9782ea83 Mon Sep 17 00:00:00 2001 From: Romain <96626929+Romanitho@users.noreply.github.com> Date: Tue, 22 Jul 2025 23:22:12 +0200 Subject: [PATCH 49/49] Update GitFlow_Make-Release-and-Sync-to-Dev.yml --- .github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml b/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml index 4cace8b11..200dca977 100644 --- a/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml +++ b/.github/workflows/GitFlow_Make-Release-and-Sync-to-Dev.yml @@ -29,11 +29,10 @@ jobs: steps: # Step 1: Checkout the code from the main branch after merge - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4 with: lfs: "true" fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} # Step 2: Extract version from branch name and calculate build number - name: Get final Release Version @@ -136,9 +135,10 @@ jobs: needs: build steps: - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4 with: fetch-depth: 0 + token: ${{ secrets.GH_PAT_SYNC }} # Step 5: Configure Git for merge back to develop - name: Configure Git