first upload
This commit is contained in:
1 parent
ea2928aac6
commit
424509aac8
5 files changed
+824
-52
No files matched your search
@@ -0,0 +1,78 @@
|
|||||||
|
# comi-cleanmgr - sources de paquet WAPT
|
||||||
|
|
||||||
|
Paquet de configuration de **N profils cleanmgr Windows**, destine a etre personnalise
|
||||||
|
avec **Lancer update-package** depuis la console WAPT. Pas de suppression de fichiers lors
|
||||||
|
de l'installation : seuls les parametres des profils sont deposes dans le registre.
|
||||||
|
|
||||||
|
## Update-package : approche native WAPT
|
||||||
|
|
||||||
|
Le code est adapte des conventions utilisees dans le paquet public officiel
|
||||||
|
[`tis-waptagent-logo-template` 1.1-15](https://wapt.tranquil.it/store/en/details/tis-waptagent-logo-template_1.1-15_windows_5.1_PROD.wapt) :
|
||||||
|
|
||||||
|
- `waptguihelper.message_dialog(...)` : introduction et validation.
|
||||||
|
- `waptguihelper.input_dialog(...)` : nombre de profils, categories additionnelles.
|
||||||
|
- `waptguihelper.grid_dialog(..., waptguihelper.GRT_ALL, MetaData)` : tableau editable des IDs/noms et grilles de categories (booleens).
|
||||||
|
- `control.inc_build()` puis `control.save_control_to_wapt()` : incrementation native du build WAPT lors d'un changement.
|
||||||
|
- Garde pour `params['install_with_luti']` ou `params['running_as_luti']` : pas d'interface sur les builds automatiques LUTI.
|
||||||
|
|
||||||
|
Les helpers `ask_control_package` et `ask_control_version` de l'exemple de Tranquil IT
|
||||||
|
sont **definis dans le fichier de l'exemple** ; ce ne sont pas des API standard.
|
||||||
|
Ici, comme le nom du paquet ne change pas et que seule sa configuration change,
|
||||||
|
`control.inc_build()` est suffisant.
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
1. Extraire l'archive comme sources WAPT, puis ouvrir le paquet dans la **console WAPT sur Windows**.
|
||||||
|
2. Depuis le depot prive, cliquer **Lancer update-package**.
|
||||||
|
3. Saisir N (1 a 50), puis renseigner **dans une seule grille** les IDs `0..9999`
|
||||||
|
et les noms des N profils.
|
||||||
|
4. Pour chacun, cocher les categories dans la grille interactive. Les `VolumeCaches`
|
||||||
|
personnalises sont disponibles dans un champ facultatif.
|
||||||
|
5. Confirmer le recapitulatif : cela reecrit `profiles.json`, incremente le numero
|
||||||
|
de build de `WAPT/control` et enregistre le controle.
|
||||||
|
6. Construire, **signer** et publier le paquet selon le flux de travail habituel,
|
||||||
|
puis l'installer sur les postes.
|
||||||
|
7. Exemples d'execution (locale, admin ou action distante autorisee) :
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
cleanmgr.exe /sagerun:4101
|
||||||
|
cleanmgr.exe /sagerun:4102
|
||||||
|
```
|
||||||
|
|
||||||
|
`update_package()` s'execute **sur le poste de l'administrateur**, pas sur les endpoints.
|
||||||
|
Les postes ne voient jamais de fenetres de configuration : `setup.py` lit seulement
|
||||||
|
`profiles.json` a l'installation.
|
||||||
|
|
||||||
|
## Fonctionnement agent
|
||||||
|
|
||||||
|
- Configuration en `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches`.
|
||||||
|
- `StateFlagsNNNN` REG_DWORD a `2` (categorie activee) ou `0` (categorie desactivee).
|
||||||
|
- Trace des IDs et de la configuration dans `HKLM\SOFTWARE\Comitari\CleanmgrProfiles`.
|
||||||
|
- `audit()` verifie les flags : `OK`, `WARNING` si categorie selectionnee absente de ce Windows,
|
||||||
|
`ERROR` si registre non conforme.
|
||||||
|
- `uninstall()` efface les valeurs de profil gerees sans toucher aux autres IDs.
|
||||||
|
- Les profils supprimes lors d'une evolution du paquet voient leurs flags nettoyes.
|
||||||
|
|
||||||
|
## Precautions
|
||||||
|
|
||||||
|
- Ne **jamais** reutiliser un ID attribue a un autre administrateur : le paquet prend
|
||||||
|
le controle des `StateFlagsNNNN` de l'ID utilise.
|
||||||
|
- Certaines categories n'existent pas sous toutes les versions de Windows.
|
||||||
|
- `Previous Installations` et `Update Cleanup` peuvent limiter le retour en arriere ;
|
||||||
|
`Recycle Bin` supprime le contenu de la corbeille.
|
||||||
|
- Le comportement sous SYSTEM peut differer de celui dans une session utilisateur.
|
||||||
|
- L'action integree **Lancer CleanMgr** de WAPT Enterprise n'est pas prouvee comme
|
||||||
|
prenant en charge les profils `4101/4102` personnalises.
|
||||||
|
- Aucune tache planifiee ou nettoyage n'est lance automatiquement par ce paquet.
|
||||||
|
- L'interface graphique n'a pas ete testee sur une console WAPT Windows reelle.
|
||||||
|
La logique a ete testee via des doubles Python des helpers, de la console et du registre.
|
||||||
|
|
||||||
|
## Contenu
|
||||||
|
|
||||||
|
- `WAPT/control` : metadonnees WAPT (minimum WAPT 2.4).
|
||||||
|
- `update_package.py` : assistant interactif de configuration, utilisant les helpers WAPT.
|
||||||
|
- `profiles.json` : donnees des profils embarquees.
|
||||||
|
- `setup.py` : application des profils sur les machines Windows, `audit`, `uninstall`.
|
||||||
|
|
||||||
|
Sources uniquement ; archive non signee, non compilable directement en `.wapt`
|
||||||
|
sans la console et la chaine de signature WAPT.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package : comi-conf-clean-manager-template
|
||||||
|
version : 0-0
|
||||||
|
architecture : all
|
||||||
|
section : base
|
||||||
|
priority : optional
|
||||||
|
name :
|
||||||
|
categories :
|
||||||
|
maintainer : root
|
||||||
|
description : Package for comi-conf-clean-manager-template
|
||||||
|
depends :
|
||||||
|
conflicts :
|
||||||
|
maturity :
|
||||||
|
locale :
|
||||||
|
target_os : all
|
||||||
|
min_wapt_version :
|
||||||
|
sources :
|
||||||
|
installed_size :
|
||||||
|
impacted_process :
|
||||||
|
description_fr :
|
||||||
|
description_pl :
|
||||||
|
description_de :
|
||||||
|
description_es :
|
||||||
|
description_pt :
|
||||||
|
description_it :
|
||||||
|
description_nl :
|
||||||
|
description_ru :
|
||||||
|
audit_schedule :
|
||||||
|
editor :
|
||||||
|
keywords :
|
||||||
|
licence :
|
||||||
|
homepage :
|
||||||
|
package_uuid :
|
||||||
|
valid_from :
|
||||||
|
valid_until :
|
||||||
|
forced_install_on :
|
||||||
|
changelog :
|
||||||
|
min_os_version :
|
||||||
|
max_os_version :
|
||||||
|
icon_sha256sum :
|
||||||
|
signer :
|
||||||
|
signer_fingerprint:
|
||||||
|
signature_date :
|
||||||
|
signed_attributes :
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
[PyScripter]
|
||||||
|
Version=3.6.4.0
|
||||||
|
|
||||||
|
[Project]
|
||||||
|
ClassName=TProjectRootNode
|
||||||
|
StoreRelativePaths=TRUE
|
||||||
|
ShowFileExtensions=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0]
|
||||||
|
ClassName=TProjectFilesNode
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0]
|
||||||
|
ClassName=TProjectFolderNode
|
||||||
|
Name=wapt
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\common.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\setuphelpers.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\setuphelpers_linux.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\setuphelpers_macos.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\setuphelpers_unix.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node5]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\setuphelpers_windows.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node6]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\wapt-get.ini
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node7]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\wapt-get.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node8]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=/opt/wapt\waptpackage.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes]
|
||||||
|
Count=9
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node1]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=$[Project-Path]changelog.txt
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node2]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=$[Project-Path]control
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node3]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=$[Project-Path]..\setup.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes\Node4]
|
||||||
|
ClassName=TProjectFileNode
|
||||||
|
FileName=$[Project-Path]..\update_package.py
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node0\ChildNodes]
|
||||||
|
Count=5
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1]
|
||||||
|
ClassName=TProjectRunConfiguationsNode
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node0]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: install
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node0\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch package installation.
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=install "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node0\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[ActiveScript-Dir]
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node1]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: remove
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node1\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch uninstallation (the uninstallation code found in local wapt database, remember to launch after an installation).
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=remove "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node1\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[ActiveScript-Dir]
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node2]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: uninstall
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node2\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch uninstallation code (for debugging purpose, it only runs the code of uninstall function).
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=uninstall "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node2\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[Project-Path]..
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node3]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: session-setup
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node3\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch session-setup (it runs directly as current user).
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=session-setup "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node3\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[ActiveScript-Dir]
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node4]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: audit
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node4\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch package audit.
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=audit -f "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node4\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[ActiveScript-Dir]
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node5]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: update-package
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node5\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch update_package (it usually serve to update binaries of the package).
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=update-package-sources "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node5\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[ActiveScript-Dir]
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node6]
|
||||||
|
ClassName=TProjectRunConfiguationNode
|
||||||
|
Name=WAPT: cleanup-session
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node6\RunConfig]
|
||||||
|
ScriptName=/opt/wapt\wapt-get.py
|
||||||
|
Description=Launch cleanup_session for the current package (it must have been installed, had session-setup and then removed).
|
||||||
|
EngineType=peRemote
|
||||||
|
ReinitializeBeforeRun=TRUE
|
||||||
|
Parameters=cleanup-session "$[Project-Path].."
|
||||||
|
WorkingDir=$[Project-Path]..
|
||||||
|
WriteOutputToFile=FALSE
|
||||||
|
OutputFileName=$[ActiveScript-NoExt].log
|
||||||
|
AppendToFile=FALSE
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes\Node6\RunConfig\ExternalRun]
|
||||||
|
Caption=External Run
|
||||||
|
Description=Run script using an external Python Interpreter
|
||||||
|
ApplicationName=$[PythonExe-Short]
|
||||||
|
Parameters=$[ActiveScript-Short]
|
||||||
|
WorkingDirectory=$[ActiveScript-Dir]
|
||||||
|
|
||||||
|
[Project\ChildNodes\Node1\ChildNodes]
|
||||||
|
Count=7
|
||||||
|
|
||||||
|
[Project\ChildNodes]
|
||||||
|
Count=2
|
||||||
|
|
||||||
|
[Project\ExtraPythonPath]
|
||||||
|
Count=0
|
||||||
|
|
||||||
@@ -1,78 +1,193 @@
|
|||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
"""WAPT agent-side configuration of cleanmgr.exe profiles.
|
||||||
|
|
||||||
|
The WAPT update_package.py wizard generates profiles.json in the package sources.
|
||||||
|
Installing this package configures VolumeCaches only: it DOES NOT run cleanup.
|
||||||
|
"""
|
||||||
|
|
||||||
from setuphelpers import *
|
from setuphelpers import *
|
||||||
|
import json
|
||||||
|
import re
|
||||||
import winreg
|
import winreg
|
||||||
|
|
||||||
uninstallkey = []
|
uninstallkey = []
|
||||||
|
|
||||||
PROFILE = 100
|
VOLUME_CACHES = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches'
|
||||||
REG_PATH = (
|
TRACK_KEY = r'SOFTWARE\Comitari\CleanmgrProfiles'
|
||||||
r'SOFTWARE\Microsoft\Windows\CurrentVersion'
|
TRACK_VALUE = 'ProfilesJson'
|
||||||
r'\Explorer\VolumeCaches'
|
PROFILE_LIMIT = 50
|
||||||
)
|
|
||||||
|
|
||||||
CATEGORIES = {
|
|
||||||
'Temporary Files',
|
|
||||||
'Thumbnail Cache',
|
|
||||||
'Delivery Optimization Files',
|
|
||||||
}
|
|
||||||
|
|
||||||
FLAG = 'StateFlags{:04d}'.format(PROFILE)
|
|
||||||
|
|
||||||
|
|
||||||
def get_categories():
|
def _validate_config(config):
|
||||||
with reg_openkey_noredir(
|
if not isinstance(config, dict) or config.get('schema_version') != 1:
|
||||||
winreg.HKEY_LOCAL_MACHINE, REG_PATH
|
raise ValueError('Invalid profiles.json schema_version')
|
||||||
) as root:
|
profiles = config.get('profiles')
|
||||||
categories = []
|
if not isinstance(profiles, list) or not 1 <= len(profiles) <= PROFILE_LIMIT:
|
||||||
index = 0
|
raise ValueError('Expected 1..%d profiles' % PROFILE_LIMIT)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for profile in profiles:
|
||||||
|
if not isinstance(profile, dict):
|
||||||
|
raise ValueError('Invalid profile')
|
||||||
|
profile_id = profile.get('id')
|
||||||
|
if type(profile_id) is not int or not 0 <= profile_id <= 9999:
|
||||||
|
raise ValueError('Profile id must be in 0..9999')
|
||||||
|
if profile_id in seen:
|
||||||
|
raise ValueError('Duplicate profile id: %d' % profile_id)
|
||||||
|
seen.add(profile_id)
|
||||||
|
|
||||||
|
name = profile.get('name')
|
||||||
|
if not isinstance(name, str) or not 1 <= len(name.strip()) <= 80:
|
||||||
|
raise ValueError('Profile name missing or too long')
|
||||||
|
categories = profile.get('categories')
|
||||||
|
if not isinstance(categories, list) or not categories:
|
||||||
|
raise ValueError('Profile %d has no categories' % profile_id)
|
||||||
|
for category in categories:
|
||||||
|
if not isinstance(category, str) or not re.fullmatch(
|
||||||
|
r'[A-Za-z0-9][A-Za-z0-9 ()_.-]{0,127}', category):
|
||||||
|
raise ValueError('Unsafe or invalid VolumeCaches key: %r' % category)
|
||||||
|
if len(set(categories)) != len(categories):
|
||||||
|
raise ValueError('Duplicate categories in profile %d' % profile_id)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def _read_deployed_config():
|
||||||
|
try:
|
||||||
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, TRACK_KEY,
|
||||||
|
sam=winreg.KEY_READ) as key:
|
||||||
|
raw = winreg.QueryValueEx(key, TRACK_VALUE)[0]
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
return _validate_config(json.loads(raw))
|
||||||
|
|
||||||
|
|
||||||
|
def _store_deployed_config(config):
|
||||||
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, TRACK_KEY,
|
||||||
|
sam=winreg.KEY_WRITE, create_if_missing=True) as key:
|
||||||
|
reg_setvalue(key, TRACK_VALUE,
|
||||||
|
json.dumps(config, ensure_ascii=False, sort_keys=True),
|
||||||
|
type=winreg.REG_SZ)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_volume_categories():
|
||||||
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, VOLUME_CACHES,
|
||||||
|
sam=winreg.KEY_READ) as root:
|
||||||
|
result = []
|
||||||
|
position = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
categories.append(winreg.EnumKey(root, index))
|
result.append(winreg.EnumKey(root, position))
|
||||||
index += 1
|
|
||||||
except OSError:
|
except OSError:
|
||||||
break
|
break
|
||||||
return categories
|
position += 1
|
||||||
|
return sorted(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _flag(profile_id):
|
||||||
|
return 'StateFlags%04d' % profile_id
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_profile_flags(profile_id, categories):
|
||||||
|
for category in categories:
|
||||||
|
path = VOLUME_CACHES + '\\' + category
|
||||||
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, path,
|
||||||
|
sam=winreg.KEY_SET_VALUE) as key:
|
||||||
|
try:
|
||||||
|
winreg.DeleteValue(key, _flag(profile_id))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_profile(profile, available):
|
||||||
|
wanted = set(profile['categories'])
|
||||||
|
flag = _flag(profile['id'])
|
||||||
|
for category in available:
|
||||||
|
path = VOLUME_CACHES + '\\' + category
|
||||||
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, path,
|
||||||
|
sam=winreg.KEY_SET_VALUE) as key:
|
||||||
|
reg_setvalue(key, flag, 2 if category in wanted else 0,
|
||||||
|
type=winreg.REG_DWORD)
|
||||||
|
unavailable = wanted - set(available)
|
||||||
|
if unavailable:
|
||||||
|
print('Profile %d (%s), missing on this Windows: %s' % (
|
||||||
|
profile['id'], profile['name'], ', '.join(sorted(unavailable))))
|
||||||
|
print('Configured profile %d (%s): %d available selected categories' % (
|
||||||
|
profile['id'], profile['name'], len(wanted & set(available))))
|
||||||
|
|
||||||
|
|
||||||
def install():
|
def install():
|
||||||
available = get_categories()
|
path = makepath(basedir, 'profiles.json')
|
||||||
|
with open(path, 'r', encoding='utf-8') as stream:
|
||||||
|
config = _validate_config(json.load(stream))
|
||||||
|
|
||||||
for category in available:
|
available = _list_volume_categories()
|
||||||
path = REG_PATH + '\\' + category
|
if not available:
|
||||||
|
raise RuntimeError('No VolumeCaches categories available on this Windows')
|
||||||
|
|
||||||
with reg_openkey_noredir(
|
previous = _read_deployed_config()
|
||||||
winreg.HKEY_LOCAL_MACHINE,
|
previous_ids = {p['id'] for p in previous['profiles']} if previous else set()
|
||||||
path,
|
new_ids = {p['id'] for p in config['profiles']}
|
||||||
sam=winreg.KEY_SET_VALUE
|
for removed_id in sorted(previous_ids - new_ids):
|
||||||
) as key:
|
_delete_profile_flags(removed_id, available)
|
||||||
value = 2 if category in CATEGORIES else 0
|
print('Removed former profile %d' % removed_id)
|
||||||
reg_setvalue(
|
|
||||||
key, FLAG, value, type=winreg.REG_DWORD
|
|
||||||
)
|
|
||||||
|
|
||||||
if category in CATEGORIES:
|
for profile in config['profiles']:
|
||||||
print('Enabled: {}'.format(category))
|
_apply_profile(profile, available)
|
||||||
|
|
||||||
missing = CATEGORIES - set(available)
|
_store_deployed_config(config)
|
||||||
if missing:
|
print('Cleanmgr profiles configured. No cleaning was executed.')
|
||||||
print('Unavailable categories: {}'.format(missing))
|
|
||||||
|
|
||||||
# Execution silencieuse du profil
|
|
||||||
run('cleanmgr.exe /sagerun:{}'.format(PROFILE),
|
def audit():
|
||||||
timeout=3600)
|
try:
|
||||||
|
config = _read_deployed_config()
|
||||||
|
if not config:
|
||||||
|
print('Cleanmgr profile configuration missing')
|
||||||
|
return 'ERROR'
|
||||||
|
available = _list_volume_categories()
|
||||||
|
if not available:
|
||||||
|
return 'ERROR'
|
||||||
|
missing = False
|
||||||
|
errors = []
|
||||||
|
for profile in config['profiles']:
|
||||||
|
wanted = set(profile['categories'])
|
||||||
|
missing_categories = wanted - set(available)
|
||||||
|
if missing_categories:
|
||||||
|
print('Profile %d unavailable categories: %s' % (
|
||||||
|
profile['id'], ', '.join(sorted(missing_categories))))
|
||||||
|
missing = True
|
||||||
|
for category in available:
|
||||||
|
path = VOLUME_CACHES + '\\' + category
|
||||||
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, path,
|
||||||
|
sam=winreg.KEY_READ) as key:
|
||||||
|
try:
|
||||||
|
current, kind = winreg.QueryValueEx(key, _flag(profile['id']))
|
||||||
|
except FileNotFoundError:
|
||||||
|
current, kind = None, None
|
||||||
|
expected = 2 if category in wanted else 0
|
||||||
|
if current != expected or kind != winreg.REG_DWORD:
|
||||||
|
errors.append('%d / %s: expected %d, got %r' % (
|
||||||
|
profile['id'], category, expected, current))
|
||||||
|
if errors:
|
||||||
|
for item in errors[:25]:
|
||||||
|
print(item)
|
||||||
|
return 'ERROR'
|
||||||
|
return 'WARNING' if missing else 'OK'
|
||||||
|
except Exception as exc:
|
||||||
|
print('Cleanmgr audit failed: %s' % exc)
|
||||||
|
return 'ERROR'
|
||||||
|
|
||||||
|
|
||||||
def uninstall():
|
def uninstall():
|
||||||
for category in get_categories():
|
previous = _read_deployed_config()
|
||||||
path = REG_PATH + '\\' + category
|
if previous:
|
||||||
|
available = _list_volume_categories()
|
||||||
with reg_openkey_noredir(
|
for profile in previous['profiles']:
|
||||||
winreg.HKEY_LOCAL_MACHINE,
|
_delete_profile_flags(profile['id'], available)
|
||||||
path,
|
with reg_openkey_noredir(winreg.HKEY_LOCAL_MACHINE, TRACK_KEY,
|
||||||
sam=winreg.KEY_SET_VALUE
|
sam=winreg.KEY_SET_VALUE) as key:
|
||||||
) as key:
|
|
||||||
try:
|
try:
|
||||||
winreg.DeleteValue(key, FLAG)
|
winreg.DeleteValue(key, TRACK_VALUE)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
print('Managed Cleanmgr profile flags removed. No cleaning was executed.')
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Interactive WAPT update_package for multiple cleanmgr profiles.
|
||||||
|
|
||||||
|
Uses the same WAPT Console conventions as Tranquil IT's
|
||||||
|
``tis-waptagent-logo-template``: waptguihelper dialogs, control.inc_build(),
|
||||||
|
and control.save_control_to_wapt(). The names ask_control_version() and
|
||||||
|
ask_control_package() in that template are local helpers, not WAPT APIs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from setuphelpers import *
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
MAX_PROFILES = 50
|
||||||
|
|
||||||
|
# Stable VolumeCaches registry subkey names, NOT translated Windows labels.
|
||||||
|
# Windows builds expose different subsets of this list.
|
||||||
|
CATALOG = [
|
||||||
|
('Temporary Files', 'Fichiers temporaires', 'Standard'),
|
||||||
|
('Thumbnail Cache', 'Miniatures', 'Standard'),
|
||||||
|
('D3D Shader Cache', 'Cache des shaders Direct3D', 'Standard'),
|
||||||
|
('Delivery Optimization Files', 'Optimisation de la distribution', 'Standard'),
|
||||||
|
('Downloaded Program Files', 'Programmes telecharges', 'Standard'),
|
||||||
|
('Internet Cache Files', 'Cache Internet de Windows', 'Standard'),
|
||||||
|
('Windows Defender', 'Fichiers non essentiels Defender', 'Maintenance'),
|
||||||
|
('Windows Error Reporting Files', 'Rapports erreurs Windows', 'Maintenance'),
|
||||||
|
('Setup Log Files', 'Journaux installation Windows', 'Maintenance'),
|
||||||
|
('Windows Upgrade Log Files', 'Journaux mises a niveau Windows', 'Maintenance'),
|
||||||
|
('Temporary Setup Files', 'Fichiers installation temporaires', 'Maintenance'),
|
||||||
|
('Old ChkDsk Files', 'Fragments CHKDSK anciens', 'Maintenance'),
|
||||||
|
('System error memory dump files', 'Dump memoire systeme', 'Diagnostic'),
|
||||||
|
('System error minidump files', 'Mini dumps systeme', 'Diagnostic'),
|
||||||
|
('Memory Dump Files', 'Autres dumps memoire', 'Diagnostic'),
|
||||||
|
('Update Cleanup', 'Nettoyage Windows Update / rollback', 'Sensible'),
|
||||||
|
('Previous Installations', 'Anciennes installations Windows / rollback', 'Sensible'),
|
||||||
|
('Recycle Bin', 'Corbeille', 'Sensible'),
|
||||||
|
('Windows ESD installation files', 'Fichiers de reinitialisation ESD', 'Sensible'),
|
||||||
|
('Device Driver Packages', 'Anciens packages pilotes', 'Sensible'),
|
||||||
|
]
|
||||||
|
|
||||||
|
PROFILE_META = json.dumps({'columns': [
|
||||||
|
{'propertyname': 'id', 'datatype': 'Integer'},
|
||||||
|
{'propertyname': 'name', 'datatype': 'String'},
|
||||||
|
]})
|
||||||
|
CATEGORY_META = json.dumps({'columns': [
|
||||||
|
{'propertyname': 'key', 'datatype': 'String'},
|
||||||
|
{'propertyname': 'description', 'datatype': 'String'},
|
||||||
|
{'propertyname': 'risk', 'datatype': 'String'},
|
||||||
|
{'propertyname': 'enabled', 'datatype': 'Boolean'},
|
||||||
|
]})
|
||||||
|
|
||||||
|
DEFAULT_SAFE_CATEGORIES = [
|
||||||
|
'Temporary Files', 'Thumbnail Cache',
|
||||||
|
'D3D Shader Cache', 'Delivery Optimization Files',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config(path):
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return {'schema_version': 1, 'profiles': []}
|
||||||
|
with open(path, 'r', encoding='utf-8') as stream:
|
||||||
|
content = json.load(stream)
|
||||||
|
if not isinstance(content, dict) or content.get('schema_version') != 1:
|
||||||
|
raise ValueError('profiles.json : schema_version invalide.')
|
||||||
|
if not isinstance(content.get('profiles'), list):
|
||||||
|
raise ValueError('profiles.json : profiles doit etre une liste.')
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def _as_rows(result, dialog_name):
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
if isinstance(result, (str, bytes)):
|
||||||
|
result = json.loads(result)
|
||||||
|
if not isinstance(result, list) or any(not isinstance(row, dict) for row in result):
|
||||||
|
raise ValueError('%s : resultat de grille invalide.' % dialog_name)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _valid_key(key):
|
||||||
|
return isinstance(key, str) and bool(re.fullmatch(
|
||||||
|
r'[A-Za-z0-9][A-Za-z0-9 ()_.-]{0,127}', key))
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_id(value):
|
||||||
|
if isinstance(value, bool):
|
||||||
|
raise ValueError('ID Cleanmgr invalide : %r' % value)
|
||||||
|
if isinstance(value, int):
|
||||||
|
profile_id = value
|
||||||
|
elif isinstance(value, str) and value.strip().isdigit():
|
||||||
|
profile_id = int(value.strip())
|
||||||
|
else:
|
||||||
|
raise ValueError('ID Cleanmgr invalide : %r (nombre entier attendu)' % value)
|
||||||
|
if not 0 <= profile_id <= 9999:
|
||||||
|
raise ValueError('ID Cleanmgr hors plage 0..9999 : %s' % profile_id)
|
||||||
|
return profile_id
|
||||||
|
|
||||||
|
|
||||||
|
def _enabled(value):
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return value
|
||||||
|
if isinstance(value, (int, float)):
|
||||||
|
return value != 0
|
||||||
|
return isinstance(value, str) and value.strip().lower() in (
|
||||||
|
'true', 'yes', 'oui', '1')
|
||||||
|
|
||||||
|
|
||||||
|
def _next_id(used):
|
||||||
|
# Keeps IDs together without overwriting existing IDs already in use.
|
||||||
|
for candidate in list(range(4101, 10000)) + list(range(0, 4101)):
|
||||||
|
if candidate not in used:
|
||||||
|
used.add(candidate)
|
||||||
|
return candidate
|
||||||
|
raise ValueError('Plus aucun identifiant Cleanmgr disponible.')
|
||||||
|
|
||||||
|
|
||||||
|
def _profile_rows(old_profiles, count):
|
||||||
|
rows = [{'id': profile['id'], 'name': profile['name']}
|
||||||
|
for profile in old_profiles[:count]]
|
||||||
|
used = {row['id'] for row in rows}
|
||||||
|
while len(rows) < count:
|
||||||
|
profile_id = _next_id(used)
|
||||||
|
rows.append({'id': profile_id, 'name': 'Profil %s' % profile_id})
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_profile_rows(rows, count):
|
||||||
|
if len(rows) != count:
|
||||||
|
raise ValueError('La grille doit contenir exactement %d profils.' % count)
|
||||||
|
profiles = []
|
||||||
|
used = set()
|
||||||
|
for row in rows:
|
||||||
|
profile_id = _normalize_id(row.get('id'))
|
||||||
|
if profile_id in used:
|
||||||
|
raise ValueError('Identifiant Cleanmgr duplique : %d.' % profile_id)
|
||||||
|
used.add(profile_id)
|
||||||
|
name = row.get('name')
|
||||||
|
if not isinstance(name, str) or not 1 <= len(name.strip()) <= 80:
|
||||||
|
raise ValueError('Le profil %d doit avoir un nom (1 a 80 caracteres).' % profile_id)
|
||||||
|
profiles.append({'id': profile_id, 'name': name.strip()})
|
||||||
|
return profiles
|
||||||
|
|
||||||
|
|
||||||
|
def _category_rows(selected):
|
||||||
|
keys = {key for key, _, _ in CATALOG}
|
||||||
|
rows = [
|
||||||
|
{'key': key, 'description': label, 'risk': risk, 'enabled': key in selected}
|
||||||
|
for key, label, risk in CATALOG
|
||||||
|
]
|
||||||
|
for key in sorted(selected - keys):
|
||||||
|
rows.append({'key': key, 'description': 'Categorie personnalisee existante',
|
||||||
|
'risk': 'A verifier', 'enabled': True})
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_categories(rows, allowed_keys):
|
||||||
|
if rows is None:
|
||||||
|
return None
|
||||||
|
if len(rows) != len(allowed_keys):
|
||||||
|
raise ValueError('Le nombre de categories de la grille a ete modifie.')
|
||||||
|
result = []
|
||||||
|
seen = set()
|
||||||
|
for row in rows:
|
||||||
|
key = row.get('key')
|
||||||
|
if key not in allowed_keys or key in seen:
|
||||||
|
raise ValueError('Cle VolumeCaches inconnue, modifiee ou dupliquee : %r' % key)
|
||||||
|
seen.add(key)
|
||||||
|
if _enabled(row.get('enabled')):
|
||||||
|
result.append(key)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _write_config(path, config):
|
||||||
|
temporary = path + '.tmp'
|
||||||
|
try:
|
||||||
|
with open(temporary, 'w', encoding='utf-8') as stream:
|
||||||
|
json.dump(config, stream, indent=2, ensure_ascii=False)
|
||||||
|
stream.write('\n')
|
||||||
|
os.replace(temporary, path)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(temporary):
|
||||||
|
os.remove(temporary)
|
||||||
|
|
||||||
|
|
||||||
|
def update_package():
|
||||||
|
import waptguihelper
|
||||||
|
|
||||||
|
# Based on official Store template: LUTI/headless builds must avoid GUI prompts.
|
||||||
|
current_params = globals().get('params', {}) or {}
|
||||||
|
if current_params.get('install_with_luti') or current_params.get('running_as_luti'):
|
||||||
|
print('Build LUTI : conservation de profiles.json sans intervention graphique.')
|
||||||
|
return 'OK'
|
||||||
|
|
||||||
|
config_path = makepath(basedir, 'profiles.json')
|
||||||
|
existing_config = _load_config(config_path)
|
||||||
|
old_profiles = existing_config['profiles']
|
||||||
|
|
||||||
|
waptguihelper.message_dialog(
|
||||||
|
'Comitari - Profils Cleanmgr',
|
||||||
|
'Cette action permet de definir plusieurs profils de nettoyage Windows.\n'
|
||||||
|
'Pour chaque profil, choisissez un identifiant, un nom et les categories.\n'
|
||||||
|
'L installation configure les profils mais NE LANCE PAS le nettoyage.\n'
|
||||||
|
'Les categories disponibles dependent de la version de Windows.',
|
||||||
|
0 + 64 # MB_OK + MB_ICONINFORMATION (comme le template officiel)
|
||||||
|
)
|
||||||
|
|
||||||
|
count_str = waptguihelper.input_dialog(
|
||||||
|
'Comitari - Profils Cleanmgr',
|
||||||
|
'Nombre de profils a configurer (1 a %d) :' % MAX_PROFILES,
|
||||||
|
str(len(old_profiles) or 2))
|
||||||
|
if count_str is None:
|
||||||
|
print('Configuration annulee (aucun fichier modifie).')
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
count = int(count_str.strip())
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise ValueError('Veuillez saisir un nombre entier de profils.')
|
||||||
|
if not 1 <= count <= MAX_PROFILES:
|
||||||
|
raise ValueError('Le nombre de profils doit etre entre 1 et %d.' % MAX_PROFILES)
|
||||||
|
|
||||||
|
# WAPT native editable grid: one dialog for N names and IDs instead of 2*N dialogs.
|
||||||
|
rows = _as_rows(waptguihelper.grid_dialog(
|
||||||
|
'Profils Cleanmgr : modifiez les identifiants et noms',
|
||||||
|
_profile_rows(old_profiles, count),
|
||||||
|
waptguihelper.GRT_ALL,
|
||||||
|
PROFILE_META), 'Profils Cleanmgr')
|
||||||
|
if rows is None:
|
||||||
|
return False
|
||||||
|
new_profiles = _validate_profile_rows(rows, count)
|
||||||
|
|
||||||
|
by_id = {p['id']: p for p in old_profiles}
|
||||||
|
for index, profile in enumerate(new_profiles):
|
||||||
|
# When an ID is changed, retain selections from its previous row.
|
||||||
|
prior = by_id.get(profile['id'])
|
||||||
|
if prior is None and index < len(old_profiles):
|
||||||
|
prior = old_profiles[index]
|
||||||
|
previous = set(prior.get('categories', [])) if prior else set(DEFAULT_SAFE_CATEGORIES)
|
||||||
|
possible = _category_rows(previous)
|
||||||
|
possible_keys = {row['key'] for row in possible}
|
||||||
|
title = 'Profil %d/%d : %d - %s' % (
|
||||||
|
index + 1, count, profile['id'], profile['name'])
|
||||||
|
|
||||||
|
returned = _as_rows(waptguihelper.grid_dialog(
|
||||||
|
title + ' - cochez les categories',
|
||||||
|
possible,
|
||||||
|
waptguihelper.GRT_ALL,
|
||||||
|
CATEGORY_META), 'Categories Cleanmgr')
|
||||||
|
if returned is None:
|
||||||
|
return False
|
||||||
|
categories = _parse_categories(returned, possible_keys)
|
||||||
|
|
||||||
|
# Add optional custom registry subkeys, when needed on specific Windows versions.
|
||||||
|
custom = waptguihelper.input_dialog(
|
||||||
|
title, 'Autres sous-cles VolumeCaches a ajouter (virgules, optionnel) :', '')
|
||||||
|
if custom is None:
|
||||||
|
return False
|
||||||
|
for candidate in custom.split(','):
|
||||||
|
category = candidate.strip()
|
||||||
|
if category and category not in categories:
|
||||||
|
categories.append(category)
|
||||||
|
if not categories:
|
||||||
|
raise ValueError('Le profil %d doit contenir au moins une categorie.' % profile['id'])
|
||||||
|
if len(categories) > 200 or any(not _valid_key(key) for key in categories):
|
||||||
|
raise ValueError('Categorie VolumeCaches invalide ou liste trop longue.')
|
||||||
|
profile['categories'] = categories
|
||||||
|
|
||||||
|
overview = '\n'.join('%d - %s : %d categories' % (
|
||||||
|
p['id'], p['name'], len(p['categories'])) for p in new_profiles)
|
||||||
|
risky = [p['name'] for p in new_profiles if
|
||||||
|
set(p['categories']) & {'Previous Installations', 'Recycle Bin',
|
||||||
|
'Windows ESD installation files', 'Update Cleanup'}]
|
||||||
|
if risky:
|
||||||
|
overview += ('\n\nATTENTION : categories sensibles dans %s.\n'
|
||||||
|
'Certaines suppressions empechent un retour a la version '
|
||||||
|
'precedente de Windows ou vident la corbeille.' % ', '.join(risky))
|
||||||
|
overview += '\n\nEnregistrer les profils dans ce paquet ?'
|
||||||
|
confirmed = waptguihelper.message_dialog(
|
||||||
|
'Validation des profils', overview,
|
||||||
|
4 + 32) # MB_YESNO + MB_ICONQUESTION
|
||||||
|
if confirmed != waptguihelper.ID_YES:
|
||||||
|
print('Configuration non validee (aucun fichier modifie).')
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_config = {'schema_version': 1, 'profiles': new_profiles}
|
||||||
|
if new_config == existing_config:
|
||||||
|
waptguihelper.message_dialog(
|
||||||
|
'Profils inchanges', 'Aucun changement detecte dans profiles.json.', 0 + 64)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# WAPT-native control API instead of manually parsing the version string.
|
||||||
|
_write_config(config_path, new_config)
|
||||||
|
control.inc_build()
|
||||||
|
control.save_control_to_wapt()
|
||||||
|
print('Profils enregistres : %s (%d profils).' % (config_path, len(new_profiles)))
|
||||||
|
print('Version du paquet WAPT : %s' % control.version)
|
||||||
|
return True
|
||||||
Reference in new issue
Block a user