diff --git a/.github/Docs/PR_And_PullRequest.md b/.github/Docs/PR_And_PullRequest.md new file mode 100644 index 00000000..738aaf43 --- /dev/null +++ b/.github/Docs/PR_And_PullRequest.md @@ -0,0 +1,583 @@ +# Guida ai Pull Request e Contributi per WinToolkit + +> **Documento Ufficiale per i Contributori** +> Repository: [MagnetarMan/WinToolkit](https://github.com/MagnetarMan/WinToolkit) +> Ultimo aggiornamento: 2026-05-22 + +--- + +## Philosophy di Progetto + +### Regola Fondamentale: 1 Issue = 1 Singolo Problema/Bug + +Ogni segnalazione o richiesta di feature deve concentrarsi su un **singolo problema**. Non mescolare richieste diverse in un'unica issue per garantire: + +- Tracciabilità precisa delle modifiche +- Revisioni più rapide e mirate +- Merge puliti e senza conflitti + +--- + +## Workflow di Contribuzione + +### Prerequisiti + +Per contribuire al progetto WinToolkit, è necessario disporre di: + +1. **Account GitHub** attivo +2. **Fork** della repository ufficiale: [MagnetarMan/WinToolkit](https://github.com/MagnetarMan/WinToolkit/fork) + +### Regole di Branching + +> [!WARNING] +> **Regola Limitazione sul Branching** +> +> - Le modifiche possono essere effettuate su branch `Dev` o su branch dedicati (`feature/*`, `fix/*`, `feat/*`, `hotfix/*`) +> - Le Pull Request verso il branch `main` verranno **chiuse immediatamente** senza preavviso +> - Il branch `Dev` è l'unico branch accettato come target per le PR +> - I branch `feature/*`, `fix/*`, `feat/*`, `hotfix/*` attivano automaticamente la pipeline leggera CI + +--- + +## Logica di Sviluppo + +### Struttura dei File + +WinToolkit utilizza una struttura modulare ben definita: + +| Tipo di Modifica | Percorso File | Descrizione | +| ----------------------------- | ------------------------- | ----------------------------------------- | +| **Funzioni/Script** | `/tool/*.ps1` | Moduli individuali del toolkit | +| **Variabili/Aspetti Globali** | `WinToolkit-template.ps1` | Template principale con variabili globali | + +### ⚠️ DIVIETO ASSOLUTO: Non Modificare Mai `WinToolkit.ps1` + +> [!WARNING] +> **File Generato Automaticamente** +> +> Il file `WinToolkit.ps1` **NON deve mai essere modificato manualmente**. Questo file è il risultato di una **pipeline automatizzata** GitHub Actions che: +> +> 1. Unisce tutti gli script dalla cartella `/tool` nel template +> 2. Esegue il **Build Bump** (incremento versione) +> 3. Esegue i **test CI/CD** +> 4. Genera la **release automatica** +> +> Qualsiasi modifica diretta a `WinToolkit.ps1` verrà sovrascritta automaticamente e sarà **respinta** durante il processo di merge. + +### Flusso di Lavoro Corretto + +``` +/tool/ → Modificare gli script individuali +WinToolkit-template.ps1 → Modificare variabili globali +WinToolkit.ps1 → NON TOCCARE MAI (generato automaticamente) +``` + +--- + +## Struttura del Progetto + +### Panoramica + +WinToolkit è organizzato in una struttura modulare che facilita lo sviluppo, la manutenzione e la distribuzione. Di seguito viene descritta l'organizzazione completa delle cartelle e dei file. + +### Albero Completo + +``` +WinToolkit/ +├── .github/ # Configurazione GitHub (CI/CD, Actions, Scripts) +│ ├── Docs/ # Documentazione del progetto +│ │ ├── PR_And_PullRequest.md # Guida alle PR (questo documento) +│ │ ├── ARCHITECTURE.md # Architettura del sistema +│ │ └── SECURITY.md # Politiche di sicurezza +│ ├── ISSUE_TEMPLATE/ # Template per le issue GitHub +│ ├── linters/ # Configurazione linter PowerShell +│ ├── scripts/ # Script di automazione build e test +│ ├── tests/ # Test automatizzati progetto +│ │ ├── Unit/ # Test unitari moduli +│ │ └── Integration/ # Test di integrazione +│ ├── workflows/ # Pipeline CI/CD GitHub Actions +│ ├── CODEOWNERS # Proprietari del codebase +│ ├── CODE_OF_CONDUCT.md # Codice di condotta della community +│ ├── CONTRIBUTING.md # Linee guida per i contributi +│ ├── dependabot.yml # Configurazione Dependabot +│ ├── FUNDING.yml # Configurazione funding +│ └── pull_request_template.md # Template per le Pull Request +│ +├── asset/ # Risorse statiche e strumenti esterni +│ ├── 7zr.exe # Estrazione archivi 7-Zip (CLI) +│ ├── AMD-Autodetect.exe # Tool rilevamento driver AMD +│ ├── Basic.xml # Configurazione Office +│ ├── DDU.zip # Display Driver Uninstaller +│ ├── dxwebsetup.exe # DirectX Web Setup +│ ├── Microsoft.PowerShell_profile.ps1 # Profilo PowerShell personalizzato +│ ├── NVCleanstall_1.19.0.exe # NVIDIA Driver Cleaner +│ ├── OOSU10.exe # O&O ShutUp10 (Debloat Windows) +│ ├── OOSU10.cfg # Configurazione O&O ShutUp10 +│ ├── Setup.exe # Setup Office 365 +│ └── speedtest.exe # Test velocità rete +│ +├── Docs/ # Documentazione tecnica +│ └── Windows Updates and the Shared Servicing Model V1.2.pdf +│ +├── img/ # Immagini e risorse grafiche +│ ├── avatar/ # Avatar contributori +│ │ └── zakkos.jpg +│ ├── Gui.jpg # Screenshot GUI Version +│ ├── RepairToolkit-old.jpg # Screenshot versione vecchia UI +│ ├── Run-old.jpg # Screenshot ver. 1.0 +│ ├── Run.jpg # Screenshot principale Readme +│ ├── WinToolkit-Dev.ico # Icona Dev +│ ├── WinToolkit-icon.png # Favicon Readme +│ └── WinToolkit.ico # Icona WinToolkit +│ +├── tool/ # Moduli funzionali del toolkit +│ ├── DisableBitlocker.ps1 # Gestione BitLocker +│ ├── GamingToolkit.ps1 # Ottimizzazioni gaming +│ ├── Install-Office.ps1 # Installazione Microsoft Office +│ ├── Repair-Office.ps1 # Riparazione Microsoft Office +│ ├── Uninstall-Office.ps1 # Disinstallazione Microsoft Office +│ ├── VideoDriverInstall.ps1 # Installazione driver video +│ ├── WinBackupDriver.ps1 # Backup driver di sistema +│ ├── WinCleaner.ps1 # Pulizia file temporanei +│ ├── WinDebloat.ps1 # Rimozione bloatware +│ ├── WinExportLog.ps1 # Esportazione log diagnostici +│ ├── WinReinstallStore.ps1 # Reinstallazione Microsoft Store +│ ├── WinRepairToolkit.ps1 # Strumenti riparazione sistema +│ └── WinUpdateReset.ps1 # Reset Windows Update +│ +├── version.json # Fonte unica di verità per versione +├── .gitignore # File ignorati da Git +├── CHANGELOG.md # Storico modifiche +├── compiler.ps1 # Sistema di compilazione modulare +├── LICENSE # Licenza MIT +├── README.md # Documentazione principale +├── start-offline.ps1 # Avvio modalità offline +├── start.ps1 # Script di avvio principale +├── TODO.md # Task e sviluppi futuri +├── WinToolkit_GUI.ps1 # Versione con interfaccia grafica WPF +├── WinToolkit-template.ps1 # Template base con funzioni core +└── WinToolkit.ps1 # File compilato finale (NON MODIFICARE) +``` + +### Descrizione Dettagliata dei Componenti + +#### Cartella `/tool/` - Moduli Funzionali + +> [!Note] +> **NOTA: Area Principale di Sviluppo** +> +> La cartella `/tool/` contiene tutti i moduli funzionali del toolkit. Ogni file PowerShell rappresenta una funzionalità specifica **sviluppabile e testabile indipendentemente**. +> +> Il compilatore inietta automaticamente ogni modulo nel template principale durante la fase di build. + +| File | Descrizione | +| ------------------------ | ------------------------------------------ | +| `DisableBitlocker.ps1` | Gestione e disabilitazione BitLocker | +| `GamingToolkit.ps1` | Ottimizzazioni specifiche per il gaming | +| `Install-Office.ps1` | Installazione e configurazione Office | +| `Repair-Office.ps1` | Riparazione installazioni Office | +| `Uninstall-Office.ps1` | Disinstallazione e rimozione Office | +| `VideoDriverInstall.ps1` | Installazione driver video avanzata | +| `WinBackupDriver.ps1` | Backup e ripristino driver di sistema | +| `WinCleaner.ps1` | Pulizia file temporanei e cache | +| `WinDebloat.ps1` | Rimozione bloatware Windows | +| `WinExportLog.ps1` | Esportazione log diagnostici per debug | +| `WinReinstallStore.ps1` | Reinstallazione Microsoft Store & WinGet | +| `WinRepairToolkit.ps1` | Strumenti di riparazione sistema (SFC/DISM)| +| `WinUpdateReset.ps1` | Reset completo Windows Update | + +#### Cartella `/asset/` - Risorse Esterne + +Contiene eseguibili e strumenti di terze parti utilizzati dal toolkit. Questi file vengono richiamati dai vari moduli in caso di necessità. + +#### Cartella `/.github/` - Infrastruttura CI/CD + +- **workflows/**: Pipeline GitHub Actions per CI/CD e distribuzione automatica + - `CI_UpdateWinToolkit_Dev.yml`: Pipeline Enterprise adattiva (Dev completa, feature/fix leggera, PR gate di qualità) + - `CI_UpdateWinToolkit_Main.yml`: Pipeline per verifica su branch main + - `Create_Release.yml`: Workflow per creazione release e generazione note + - `Release_Wintoolkit.yml`: Pipeline per creazione branch release e merge in main + - `security.yml`, `stale.yml`: Workflow di manutenzione +- **scripts/**: Script PowerShell per build e test automatici + - `Update-Version.ps1`: Gestione versione del progetto + - `Invoke-Build.ps1`: Wrapper ufficiale del compilatore con statistiche compressione + - `Test-CompiledScript.ps1`: Validazione post-compilazione (sintassi, funzioni, menu, dimensione, encoding) + - `New-ReleaseNotes.ps1`: Generazione note di release +- **tests/**: Test automatizzati progetto + - `WinToolkit.Tests.ps1`: Test suite Pester 5 per validazione moduli e funzionalità + - **Unit/**: Test unitari per singoli moduli (VideoDriver, GamingToolkit, WinCleaner) + - **Integration/**: Test di integrazione (Build.Tests.ps1) +- **linters/**: Configurazione PSScriptAnalyzer +- **Docs/**: Documentazione ufficiale progetto +- **ISSUE_TEMPLATE/**: Template per le issue GitHub + - `bug_report.yml`: Template per segnalazione bug + - `enhancement.yml`: Template per miglioramenti + - `feature_request.yml`: Template per nuove funzionalità +- **CODE_OF_CONDUCT.md**: Codice di condotta della community +- **CONTRIBUTING.md**: Linee guida per i contributi +- **pull_request_template.md**: Template per le Pull Request +- **CODEOWNERS**: Proprietari del codebase + +#### File Radice + +| File | Ruolo | +| ------------------------- | ---------------------------------------------------------------------- | +| `version.json` | Fonte unica di verità per versione e build number | +| `WinToolkit-template.ps1` | Template base con funzioni core, logging e UI (MODIFICABILE) | +| `WinToolkit.ps1` | File compilato finale distribuito (GENERATO AUTOMATICAMENTE) | +| `compiler.ps1` | Sistema di compilazione ufficiale con tokenizer e minificazione sicura| +| `WinToolkit_GUI.ps1` | Versione con interfaccia grafica WPF | +| `start.ps1` | Entry point ufficiale per distribuzione one-liner | +| `start-offline.ps1` | Modalità di avvio senza connessione internet | +| `TODO.md` | Task e sviluppi futuri | + +--- + +## 🧪 Test delle Versioni Compilate + +Dopo aver effettuato delle modifiche, è obbligatorio testare la versione compilata di WinToolkit prima di aprire una Pull Request. Sono disponibili due modalità di test completamente autonome. + +--- + +### 🟢 Modalità 1: Test Automatico tramite GitHub Workflows (Consigliato) + +Questa modalità utilizza la stessa pipeline ufficiale di build direttamente nella tua fork, garantendo che il tuo codice funzioni esattamente come nel repository principale. + +#### ✅ Prerequisiti + +1. Fork del repository WinToolkit sul tuo account GitHub +2. Branch `Dev` presente e aggiornato nel tuo fork +3. Nessuna restrizione sulle GitHub Actions nella tua fork + +#### 🔄 Trigger della Pipeline + +La pipeline si attiva automaticamente su: + +- **Push su Dev** → Pipeline completa: lint → test → build +- **Push su feature/fix/feat/hotfix/*** → Pipeline leggera: lint → test → build → commit +- **PR verso Dev** → Gate di qualità: lint → test (no build, no deploy) + +I trigger sono limitati ai file: + +- `tool/*.ps1` +- `WinToolkit-template.ps1` +- `compiler.ps1` +- `start-offline.ps1` +- `start.ps1` +- `.github/workflows/*.yml` +- `.github/tests/*.ps1` +- `.github/scripts/*.ps1` + +#### 🛡️ PR Security Guard — Politica a Tre Livelli + +Ogni PR verso Dev viene analizzata automaticamente da un sistema di sicurezza a tre livelli: + +**Livello 1 — Consentiti (silenzioso)** + +- File in `tool/*` +- `WinToolkit.ps1` (solo per maintainer) +- ✅ PR procede normalmente senza interventi + +**Livello 2 — Consentiti con Warning (revisione manuale)** + +- `start.ps1`, `WinToolkit_GUI.ps1`, `WinToolkit-template.ps1`, `asset/*` +- ⚠️ PR rimane aperta, viene aggiunto un commento di avviso per il maintainer + +**Livello 3 — Protetti (blocco totale)** + +- Tutti gli altri file (`.github/**`, `compiler.ps1`, ecc.) +- ⛔ PR chiusa automaticamente con commento di accesso negato + +> [!WARNING] +> **Regola per i contributori esterni**: Puoi proporre modifiche **esclusivamente** ai moduli nella cartella `tool/`. Per modifiche ai file core (compiler, workflow CI/CD, script di build), apri una **Issue** descrivendo la proposta. + +#### 📋 Passaggi Configurazione + +1. Vai nella pagina del tuo fork su GitHub +2. Naviga in **Settings > Actions > General** +3. Imposta **Actions permissions** su `Allow all actions and reusable workflows` +4. Abilita **Read and write permissions** nella sezione Workflow permissions +5. Salva le modifiche + +#### 🔄 Comandi Esecuzione + +```bash +# 1. Assicurati di essere sul branch Dev +git checkout Dev + +# 2. Effettua le tue modifiche ai moduli in /tool/ o al template + +# 3. Committa e pusha direttamente sul branch Dev del tuo fork +git add . +git commit -m "- Descrizione modifiche" +git push origin Dev +``` + +#### ⚙️ Funzionamento Automatico + +Appena pushati: + +1. Il workflow `CI_UpdateWinToolkit_Dev.yml` si avvierà automaticamente +2. Verranno eseguiti **nella tua fork**: + - ✅ Controllo sicurezza sulle modifiche (PR Security Guard a 3 livelli) + - ✅ Linting completo con PSScriptAnalyzer + - ✅ Test suite Pester 5 (validazione moduli e funzionalità) + - ✅ Validazione sintassi AST (compiler.ps1 e template) + +> [!IMPORTANT] +> **Nota Fondamentale**: La pipeline su push esegue lint, test e build. Il versioning ufficiale avviene esclusivamente tramite il workflow `Create_Release.yml` (manualmente attivato). Nelle fork, i job di build committano il file `WinToolkit.ps1` generato. + +#### ✅ Verifica Risultato + +1. Vai nella tab **Actions** del tuo fork +2. Controlla che il workflow sia completato con successo (✅) +3. Se tutti i controlli passano, il tuo codice è valido e compatibile +4. Per ottenere il file `WinToolkit.ps1` compilato usa la **Modalità 2 Offline** +5. Quando aprirai la PR verso il repository ufficiale, l'intera pipeline verrà eseguita automaticamente + +> [!Tip] +> Se il workflow fallisce, consulta i log dettagliati per identificare l'errore. I controlli di sicurezza bloccheranno automaticamente PR che modificano file al di fuori della cartella `/tool`. + +--- + +### 🔵 Modalità 2: Test Offline Locale con compiler.ps1 + +Questa modalità permette di compilare e testare WinToolkit completamente offline senza necessità di pushare su GitHub. + +#### ✅ Prerequisiti Sistema + +- Windows 10 1809+ / Windows 11 22H2+ +- PowerShell 5.1 o PowerShell 7+ +- Privilegi Amministratore +- Nessuna dipendenza esterna richiesta + +#### 📋 Passaggi Esecuzione + +1. Apri PowerShell come Amministratore +2. Naviga nella cartella root del repository: + +```powershell +cd C:\Percorso\A\WinToolkit +``` + +3. Esegui il compilatore ufficiale: + +```powershell +.\compiler.ps1 +``` + +#### ⚙️ Fasi Compilazione + +Il compilatore eseguirà automaticamente queste operazioni: + +1. Validazione prerequisiti e struttura cartelle +2. Caricamento template e tutti i moduli da `/tool/` +3. Iniezione automatica di ogni funzione nel template +4. Minificazione sicura tramite parser ufficiale PowerShell +5. Verifica sintassi file finale +6. Generazione dashboard statistiche + +#### ✅ Verifica Risultato + +Al termine della compilazione verrà mostrato un report con: + +- Numero moduli processati +- Dimensione sorgente vs finale +- Percentuale di compressione +- Tempo totale compilazione + +Per testare il file generato: + +```powershell +# Esegui direttamente il file compilato +.\WinToolkit.ps1 +``` + +> [!Note] +> In caso di errori durante la compilazione, il compilatore mostrerà l'esatto punto di errore e ripristinerà automaticamente lo stato precedente. Non verrà generato un file corrotto. + +--- + +## 🏷️ Tag per la Categorizzazione delle Pull Request + +> [!IMPORTANT] +> **Obbligatorio: Usa i tag corretti** +> +> Quando apri una Pull Request, è fondamentale categorizzarla correttamente tramite uno dei tag supportati. +> I tag permettono al maintainer di identificare rapidamente la natura della modifica e di gestire le PR in modo ordinato. +> L'uso scorretto dei tag comporta il rifiuto o il rinvio della PR fino alla correzione. + +### Tag Supportati + +| Tag | Categoria | Descrizione | +| ------------------ | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `BUG` | Bug Fixes | Correzione di un errore o comportamento difettoso già presente nel codice. La PR deve riferirsi a una issue esistente. | +| `ENHANCEMENT` | Miglioramento Funzione | Modifica o miglioramento di una funzione già esistente che ne aumenta la qualità, la velocità o la robustezza. | +| `FEATURE` | Aggiunta Funzionalità Richiesta non presente | Implementazione di una nuova funzionalità precedentemente richiesta e non ancora disponibile nel toolkit. | +| `GUI` | Modifica GUI | Qualsiasi modifica all'interfaccia grafica (`WinToolkit_GUI.ps1`) o ai componenti visuali del toolkit. | +| `External Changes` | Modifiche dei Contributori Esterni | Modifica proposta da un collaboratore esterno che non fa parte del team di sviluppo diretto. Riservato al maintainer. | + +### Come Usare i Tag + +Inserisci il tag all'inizio del titolo della Pull Request, separato da uno `/` o `-` dalla descrizione: + +``` +BUG/corretto crash su WinCleaner durante pulizia cartella Temp +ENHANCEMENT/ottimizzato algoritmo ordinamento in GamingToolkit +FEATURE/aggiunta opzione backup automatico in WinBackupDriver +GUI/aggiustato layout finestra principale di WinToolkit_GUI +``` + +> [!TIP] +> È possibile combinare il tag con il prefisso del branch (es. `fix/BUG/nome`) ma **non è obbligatorio**. +> La pipeline CI riconosce entrambe le forme e processa la PR correttamente. + +--- + +## Standard dei Commit + +### Requisiti per i Messaggi di Commit + +Ogni commit deve seguire questa struttura: + +- **Descrizione in elenco puntato** delle modifiche effettuate. +- **Chiara e concisa**: massimo 72 caratteri per la prima riga. +- **In italiano** per mantenere coerenza con il progetto. + +### Esempi di Commit Validi + +```bash +# Esempi di commit corretti +- Aggiunta funzione di esportazione log in WinExportLog.ps1. +- Corretto bug sul parsing delle variabili d'ambiente. +- Implementato supporto per Windows 11 24H2. +- Ottimizzato algoritmo di pulizia in `WinCleaner.ps1`. +- Aggiornata documentazione delle variabili globali. +``` + +--- + +## Bug Reporting + +### Procedura di Segnalazione Bug + +> [!Note] +> **Informazioni Utili per la Risoluzione** +> +> In caso di bug fix, è **caldamente consigliato** allegare il file `.zip` dei log ottenuto tramite la funzione **"Export Log"** del toolkit. Questo accelera significativamente il processo di debug e risoluzione. + +### Informazioni da Includere + +Quando segnali un bug, includi: + +1. **Descrizione chiara** del problema. +2. **Passaggi per riprodurre** il bug. +3. **Output atteso** vs **output effettivo**. +4. **File .zip dei log** (se applicabile). +5. **Versione del sistema operativo** Windows in uso. +6. **Versione di WinToolkit** utilizzata. + +--- + +## Gestione Milestone + +### Tipologie di Milestone + +Il progetto utilizza due categorie principali per la gestione delle task: + +| Milestone | Descrizione | Tipologia | +| ------------------------ | ------------------------------------------------------------- | -------------- | +| **Dev** | Branch di lavoro per sviluppo, test e integrazione continua | Target PR | +| **main** | Branch di distribuzione, contiene solo WinToolkit.ps1 compilato| Release | +| **Backlog** | Problemi complessi, nuove feature, discussioni architecturali | Bassa Priorità | + +### Criteri di Assegnazione + +- **Dev**: Tutti i contributi vanno diretti a questo branch. Qui si svolge lo sviluppo e i test. +- **main**: Branch protetto. L'output compilato (`WinToolkit.ps1`) viene qui committato tramite release. +- **Backlog**: Feature request complesse, refactoring significativi, discussioni che richiedono valutazione approfondita. + +--- + +## Passi Rapidi per Contribuire + +### Step 1: Fork della Repository + +1. Accedi a [MagnetarMan/WinToolkit](https://github.com/MagnetarMan/WinToolkit) +2. Clicca sul pulsante **"Fork"** in alto a destra +3. Seleziona il tuo account GitHub come destinazione + +### Step 2: Clona il Fork Locale + +```bash +git clone https://github.com/TUO_USERNAME/WinToolkit.git +cd WinToolkit +``` + +### Step 3: Configura il Remote Upstream + +```bash +git remote add upstream https://github.com/MagnetarMan/WinToolkit.git +``` + +### Step 4: Crea il Branch di Lavoro + +```bash +git checkout Dev +git pull upstream Dev +git checkout -b fix/nome-del-fix +``` + +> [!TIP] +> **Pattern di branch supportati dalla pipeline**: +> +> - `fix/*` o `bugfix/*` → Pipeline leggera (lint + test + build) +> - `feature/*` o `feat/*` → Pipeline leggera (lint + test + build) +> - `hotfix/*` → Pipeline leggera (lint + test + build) +> - `BUG/*`, `ENHANCEMENT/*`, `FEATURE/*` → Pipeline leggera (lint + test + build) +> - `Dev` → Pipeline completa (lint + test + build) +> +> I nomi dei branch devono seguire questi pattern per attivare automaticamente la CI. + +### Step 5: Effettua le Modifiche + +> [!Note] +> **NOTA: Ricorda la Logica di Sviluppo** +> +> - Modifica gli script in `/tool/*.ps1` per le funzionalità. +> - Modifica `WinToolkit-template.ps1` per le variabili globali. +> - **NON toccare mai `WinToolkit.ps1`**. + +### Step 6: Committa le Modifiche + +```bash +git add . +git commit -m "- Descrizione chiara della modifica +- Secondo punto se necessario" +``` + +### Step 7: Push e Pull Request + +```bash +git push origin fix/nome-del-fix +``` + +1. Vai su GitHub nella **tua** repository forked. +2. Clicca su **"Compare & pull request"**. +3. Assicurati che il branch base sia **`DEV`** (non `main`!). +4. Compila il template della PR con tutti i dettagli richiesti. +5. Clicca **"Create pull request"**. + +--- + +## Risorse Aggiuntive + +- **Documentazione Ufficiale**: [README.md](/README.md). +- **Changelog**: [CHANGELOG.md](/CHANGELOG.md). +- **Issue Tracker**: [Issues](https://github.com/MagnetarMan/WinToolkit/issues). + +--- + +## **Grazie per contribuire a WinToolkit!** + +Il tuo contributo è fondamentale per migliorare questo strumento per tutta la comunità. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3524f3af..1010088a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,132 @@ Il formato si basa su [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) e --- +## [2.5.4] - CODENAME: "Deborah" - 2026-05-30 ([#114](https://github.com/MagnetarMan/WinToolkit/issues/114)) + +### Aggiunte + +- **start.ps1** + - Aggiunto countdown di 5 secondi prima della chiusura dello script. Adesso alla fine dell'installazione lo script si chiude automaticamente se ogni operazione è stata eseguita con successo. +- **Profilo PowerShell** + - Aggiunta funzione caricamento WinToolkit-GUI. +- **WinCleaner** + - Aggiunta funzione di debloat del modello AI Locale di Google che si installa all'insaputa dell'utente. [[Video]](https://www.youtube.com/watch?v=vWNfSGPivHQ) + +### Correzioni + +- **WinToolkit-template.ps1** + - Aggiunta funzione pulizia log eventi. [[#104](https://github.com/MagnetarMan/WinToolkit/issues/104) [@Magnetarman]](https://github.com/Magnetarman) + - Fix testo spinner in `Invoke-WithSpinner`. [[#112](https://github.com/MagnetarMan/WinToolkit/issues/112) [@Magnetarman]](https://github.com/Magnetarman) + - Fix assert funzioni admin. [[#105](https://github.com/MagnetarMan/WinToolkit/issues/105) [@Magnetarman]](https://github.com/Magnetarman) + - Fix pulizia `WinCleaner`. [[#103](https://github.com/MagnetarMan/WinToolkit/issues/103) [@Magnetarman]](https://github.com/Magnetarman) + - Fix errore avvio UniGetUI. [[#102](https://github.com/MagnetarMan/WinToolkit/issues/102) [@Magnetarman]](https://github.com/Magnetarman) + - Fix avvisi sicurezza durante i test. [[#101](https://github.com/MagnetarMan/WinToolkit/issues/101) [@Magnetarman]](https://github.com/Magnetarman) + - Fix regex case-insensitive nel compiler. [[#90](https://github.com/MagnetarMan/WinToolkit/issues/90) [@Magnetarman]](https://github.com/Magnetarman) + - Fix etichettatura toolkit. [[#89](https://github.com/MagnetarMan/WinToolkit/issues/89) [@Magnetarman]](https://github.com/Magnetarman) + - Fix rollback `compiler.ps1`. [[#88](https://github.com/MagnetarMan/WinToolkit/issues/88) [@Magnetarman]](https://github.com/Magnetarman) + - Fix perdita privacy log. [[#87](https://github.com/MagnetarMan/WinToolkit/issues/87) [@Magnetarman]](https://github.com/Magnetarman) + - Refactor completo `Office Toolkit`. [[#86](https://github.com/MagnetarMan/WinToolkit/issues/86) [@Magnetarman]](https://github.com/Magnetarman) + - Fix test falliti WinToolkit. [[#76](https://github.com/MagnetarMan/WinToolkit/issues/76) [@Magnetarman]](https://github.com/Magnetarman) + +- **start.ps1** + - Rimossa variabile `$rebootNeeded`. + - Spostati CLSID Windows Terminal e lista processi interferenti Winget in `$script:AppConfig`. + - Rimosso stile Progress da `$Global:MsgStyle`. + - Eliminata funzione `Install-NuGetIfRequired` e il suo pre-check ridondante in `Install-WingetPackage`. + - Rimossa una chiamata ridondante a `Update-EnvironmentPath` prima del fallback MSIX. + - Rimosso if con warning non bloccante in `Test-VCRedistInstalled`. + - Corretta numerazione passi in `Repair-WingetDatabase`. + - Sostituito valore non valido Progress con Info nel parametro -Type di Write-StyledMessage (risolve errore ValidateSet runtime). + - Install-GitPackage - Sostituite 3 occorrenze di aggiornamento PATH inline con la funzione `Update-EnvironmentPath` esistente. + - Invoke-WinToolkitSetup - Rimossa duplicazione codice rilevamento percorso pwsh.exe (ora rilevato una sola volta). + - Estrazione funzioni annidate: + - `Get-WingetDownloadUrl` estratta da `Install-WingetCore`. + - `Install-NerdFontsLocal` estratta da `Install-PspEnvironment`. + - `Get-ProfileDirLocal` estratta da `Install-PspEnvironment`. + - Aggiunta lista UpdateServices in $script:AppConfig e aggiornate Invoke-StopUpdateServices / Invoke-StartUpdateServices. + - Spostato $Global:MsgStyles all'interno di AppConfig eliminando scope globale non necessario. + - Corretto pattern ProgressPreference in Install-WingetPackage (ora salva e ripristina valore originale). + - Uniformato operatore negazione da ! a -not per coerenza. + - Corretta numerazione passi mancante (#6) in Repair-WingetDatabase. + - Aggiunto Layout.Width in configurazione, rimossi magic number 65 hardcoded. + - Rimossa inizializzazione superflua `$downloadUrl = $null` a riga 1127. + - Aggiunto blocco .SYNOPSIS a tutte le 20 funzioni presenti. + - Riorganizzato il codice in modo più pulito e lineare. + - Rimuovi tutti i caratteri ANSI/colori prima di salvare su file. [[Thanks To @Ennio Costanzi]]() + - Corretti errori di parsing funzione non correttamente inizializzata. [[Thanks To @Ennio Costanzi]]() + +- **WinToolkit-template.ps1** + - Sostituita la chiusura dei processi duplicata nel ripristino di Winget integrando la funzione `Stop-ToolkitProcesses`. + - Consolidata e de-duplicata registrazione `AppxManifest.xml` tramite funzione interna dedicata. + - Aggiunto caching a `Get-SystemInfo` azzerando latenze CIM durante il ricarico del menu principale. + - Inserita funzione `Initialize-ToolkitPaths` centralizzata per i folder log/temp, chiamata fuori ciclo prima della UI. + - Ottimizzato wrapper custom `Read-Host` tramite interruzione bloccante `ReadKey()` cancellando overhead della CPU nel polling loop. + - Uniformati link e blocchi di configurazioni `AppConfig` centrali. + - Gestione Servizi: Aggiunte le funzioni Invoke-StopUpdateServices e Invoke-StartUpdateServices per sospendere temporaneamente wuauserv, bits, cryptsvc e dosvc. + - Integrazione: Il sistema ora arresta i servizi subito dopo i controlli preliminari e li riavvia automaticamente in ogni scenario di uscita (completamento, riavvio in PowerShell 7/Terminal o errore critico). + - Feedback Utente: Inseriti messaggi di stato per informare correttamente l'utente durante l'arresto e il riavvio dei servizi. + - Introdotta funzione `Test-WindowsUpdateStatus` per rilevare gli aggiornamenti di Windows in sospeso e l'attività del programma di installazione. + - Rimuovi tutti i caratteri ANSI/colori prima di salvare su file. [[Thanks To @Ennio Costanzi]]() + - Corretti bug in `Get-UserConfirmation` e `Read-ValidatedChoice`. + - Aggiornato menu principale di WinToolkit per sfruttare `Read-ValidatedChoice`. + - Adeguati i vari script per utilizzare la nuova funzione Invoke-WithSpinner globale, eliminando le vecchie funzioni Write-Spinner/Stop-Spinner e Write-Wait. + - Adeguati i commenti interni in modo che siano coerenti con le modifiche effettuate. + - Aggiornata funzione di Log e resa globale in modo da limitare chiamate multiple durante l'avvio di ogni singola funzione. + +- **WinRepairToolkit** + - Improve AppX registration and chkdsk handling. [[Thanks To @Ennio Costanzi]]() + - Controllo iniziale stato sistema: Aggiunta funzione `Test-PendingOperations` che verifica chiavi di registro per reboot pendente e avvisa l'utente prima di iniziare le riparazioni. + - Pulizia stato DISM: Esecuzione automatica di DISM /CancelCommands prima di ogni operazione /StartComponentCleanup per annullare operazioni pendenti. + - Gestione specifica errore: 0x800f0806 viene riconosciuto come non critico, viene mostrato un avviso informativo e non viene conteggiato come errore. + - Supporto codice exit 3010: DISM /ResetBase che ritorna 3010 (reboot richiesto) viene considerato successo. + - Esclusione errore dal conteggio: 0x800f0806 viene saltato nella logica di rilevazione errori generale. + - Rimossa funzione registrazioni AppX in quanto il Fix temporaneo non risulta più necessario con gli ultimi cumulativi di Windows 11. + +- **compiler.ps1** + - Corretti errori di parsing funzione non correttamente inizializzata. + +- **README.md** + - Aggiunto banner conto totale dei Download [Diventera realistico quando la versione GUI verrà ultimata]. + +### Modifiche + +- **WinToolkit-template.ps1** + - Aggiunta funzione `Get-UserConfirmation` (sostituisce `Read-YesNoPrompt`). + - Aggiunta funzione `Read-ValidatedChoice` (gestisce input multipli e validazione avanzata). + - Rimosse funzioni deprecate come `Get-UserConfirmation`, aggiornamento dei vari script al nuovo paradigma completo. + +- **VideoDriverInstall.ps1** + - Upgrade script installazione driver video. [[#109](https://github.com/MagnetarMan/WinToolkit/issues/109) [@Magnetarman]](https://github.com/Magnetarman) + - Refactor `Video Driver Toolkit`. [[#108](https://github.com/MagnetarMan/WinToolkit/issues/108) [@Magnetarman]](https://github.com/Magnetarman) + +- **Pipeline CI/CD** + - Upgrade workflow pipeline CI/CD. [[#91](https://github.com/MagnetarMan/WinToolkit/issues/91) [@Magnetarman]](https://github.com/Magnetarman) + - Pipeline CI/CD v 3.1.0. [[#77](https://github.com/MagnetarMan/WinToolkit/issues/77) [@Magnetarman]](https://github.com/Magnetarman) + +- **WinToolkit-template.ps1** + - Refactor del template principale. [[#81](https://github.com/MagnetarMan/WinToolkit/issues/81) [@Magnetarman]](https://github.com/Magnetarman) + +- **Office Toolkit** + - Upgrade debloat dopo installazione. [[#80](https://github.com/MagnetarMan/WinToolkit/issues/80) [@Magnetarman]](https://github.com/Magnetarman) + - Office uninstall SaRA con comando Get Help. [[#79](https://github.com/MagnetarMan/WinToolkit/issues/79) [@Magnetarman]](https://github.com/Magnetarman) + - Rework `Office Toolkit`. [[#78](https://github.com/MagnetarMan/WinToolkit/issues/78) [@Magnetarman]](https://github.com/Magnetarman) + +- **Profilo PowerShell** + - Aggiunta breakline in istruzione if. [[#99](https://github.com/MagnetarMan/WinToolkit/issues/99) [@pomodori92]](https://github.com/pomodori92) + - Status supporto versioni Windows nel README. [[#96](https://github.com/MagnetarMan/WinToolkit/issues/96) [@pomodori92]](https://github.com/pomodori92) + - Rimosse variabile inutilizzata da `WinToolkit-GUI.ps1`. [[#74](https://github.com/MagnetarMan/WinToolkit/issues/74) [@pomodori92]](https://github.com/pomodori92) + - Testi help per Safe, Warning e Alert nel profilo PowerShell. [[#73](https://github.com/MagnetarMan/WinToolkit/issues/73) [@pomodori92]](https://github.com/pomodori92) + - Modifiche `WinToolkit-GUI.ps1`. [[#72](https://github.com/MagnetarMan/WinToolkit/issues/72) [@pomodori92]](https://github.com/pomodori92) + - Refactor check `Install-GitPackage` e rimossa variabile `wingetDeepCheck`. [[#67](https://github.com/MagnetarMan/WinToolkit/issues/67) [@pomodori92]](https://github.com/pomodori92) + - Check se `$resolveWingetPath` è null e messaggi di errore. [[#66](https://github.com/MagnetarMan/WinToolkit/issues/66) [@pomodori92]](https://github.com/pomodori92) + - Comando winget uninstall per includere tutte le versioni PowerShell. [[#65](https://github.com/MagnetarMan/WinToolkit/issues/65) [@pomodori92]](https://github.com/pomodori92) + - PowerShell aggiornato con successo, ma dice "Installazione interrotta". [[#75](https://github.com/MagnetarMan/WinToolkit/issues/75) [@pomodori92]](https://github.com/pomodori92) + - Timeout riparazioni a 3 ore. [[#71](https://github.com/MagnetarMan/WinToolkit/issues/71) [@pomodori92]](https://github.com/pomodori92) + - OfficeTool: barra file eliminati appare sotto le opzioni menu. [[#68](https://github.com/MagnetarMan/WinToolkit/issues/68) [@pomodori92]](https://github.com/pomodori92) + - Dopo aver selezionato l'opzione 3, si blocca durante il ripristino Winget. [[#59](https://github.com/MagnetarMan/WinToolkit/issues/59) [@pomodori92]](https://github.com/pomodori92) + +--- + ## [2.5.3] - CODENAME: "Deborah" - 2026-04-03 ([#64](https://github.com/MagnetarMan/WinToolkit/issues/64)) ### Aggiunte diff --git a/README.md b/README.md index 8e639d79..ca87db7d 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,13 @@

license - versione + versione last-commit Update WinToolkit Commit Activity Main + downloads-main

-![Maintenance](https://magnetarman.com/countdown.php) - Run-banner WinToolkit è una suite di script PowerShell potente e compatta, progettata per offrire a professionisti IT, amministratori di sistema e utenti esperti un controllo granulare sulla manutenzione e sulla risoluzione dei problemi di Windows e della Suite Office. Questo toolkit intuitivo aggrega gli strumenti di riparazione di sistema più efficaci in un'unica interfaccia, automatizzando i processi complessi per ottimizzare le prestazioni e ripristinare la stabilità del sistema con pochi passaggi automatizzati. Questo progetto è traslitterato tramite un workflow AI. @@ -27,14 +26,14 @@ WinToolkit è una suite di script PowerShell potente e compatta, progettata per > - **spazio libero su disco**: >= 50 GB [(vedere la sezione F.A.Q.)](#-faq---domande-frequenti); > - **Windows >= 8.1**. -| Versioni di Windows | Supportato | -| :-------------------- | :------------------ | -| Windows 11 >= 22H2 | 🟢 Sì | -| Windows 11 <= 21H2 | 🟡 Sì con eccezioni | -| Windows 10 >= 1809 | 🟢 Sì | -| Windows 10 <= 1809 | 🟠 Parzialmente | -| Windows 8.1 | 🟠 Parzialmente | -| Windows 8 e inferiori | 🔴 No | +| Versioni di Windows | Supportato | +| :-------------------- | :-------------- | +| Windows 11 >= 22H2 | 🟢 Sì | +| Windows 10 >= 1809 | 🟢 Sì | +| Windows 11 <= 21H2 | 🟡 Parzialmente | +| Windows 10 <= 1809 | 🟡 Parzialmente | +| Windows 8.1 | 🟡 Parzialmente | +| Windows 8 e inferiori | 🔴 No | ## 🚀 Come eseguire WinToolkit @@ -74,16 +73,29 @@ irm https://magnetarman.com/WinToolkit | iex > Avviare le versioni in fase di sviluppo è **rischioso e potrebbe causare danni al tuo sistema.** Sono presenti funzionalità in corso di sviluppo e/o in fase di test. Se non sei sicuro o non sai cosa stai facendo, vai alla sezione "Esecuzione consigliata". ```powershell -irm https://magnetarman.com/WinToolkit-Dev | iex +irm https://magnetarman.com/winstart-dev | iex +``` + +## 🪟 GUI - Interfaccia grafica + +> [!CAUTION] +> La Versione GUI è disponibile in versione ALPHA, pertanto potrà subire modifiche anche significative. Il funzionamento di questa versione è fortemente instabile, utilizzare a proprio rischio e pericolo. + +```powershell +irm https://magnetarman.com/Wintoolkit-gui | iex ``` --- ## 👾 Componenti -- **Windows Repair Toolkit**: Avvia una sequenza automatizzata di comandi standard di Windows, come: sfc, chkdsk e DISM per individuare e correggere la corruzione dei file di sistema e i problemi del disco. -- **Windows Update Reset**: Risolve in modo efficiente i problemi comuni di Windows Update resettando i componenti chiave e ripristinando le impostazioni dei servizi. -- **Office Toolkit**: Semplifica l'installazione, la riparazione e la rimozione dei componenti o del prodotto intero. È possibile installare una versione "Basic" di Microsoft Office in modo semi-automatico, riparare le installazioni esistenti con due diverse modalità (Riparazione rapida offline e riparazione completa online), oppure rimuovere completamente il software dal sistema utilizzando l'efficace strumento ufficiale "Microsoft Support and Recovery Assistant (SaRA)". +- **Sezione Windows**: + - **Windows Repair Toolkit**: Avvia una sequenza automatizzata di comandi standard di Windows, come: sfc, chkdsk e DISM per individuare e correggere la corruzione dei file di sistema e i problemi del disco. + - **Windows Update Reset**: Risolve in modo efficiente i problemi comuni di Windows Update resettando i componenti chiave e ripristinando le impostazioni dei servizi. +- **Sezione Office**: + - **Install Office**: Consente di installare una versione "Basic" di Microsoft Office in modo semi-automatico. + - **Repair Office**: Ripara le installazioni esistenti offrendo la modalità rapida offline o completa online. + - **Uninstall Office**: Rimuove completamente la suite dal sistema utilizzando l'efficace strumento ufficiale "GetHelpCMD" (EX saRA). - **Windows Store Repair**: Esegue una reinstallazione di componenti critici come: Microsoft Store, WinGet, e UniGet UI (Utile per aggiornare e gestire le app in modo grafico utilizzando WinGet). - **Win Backup Driver**: Semplifica il processo di backup dei driver, che automatizza l'esportazione di tutti i driver di terze parti installati utilizzando il comando DISM per garantire un'operazione completa e affidabile. - **Cleaner Toolkit**: Libera spazio su disco e ottimizzare le prestazioni eseguendo una pulizia profonda. @@ -105,18 +117,9 @@ irm https://magnetarman.com/WinToolkit-Dev | iex --- -## 🪟 Coming soon: GUI - Interfaccia grafica - -> [!CAUTION] -> L'interfaccia grafica mostrata di seguito è un'anteprima, pertanto potrà subire modifiche anche significative. La sua condivisione risponde al forte interesse degli utenti verso una futura implementazione. -> Gui-Mode-banner - ---- - ## 📌 Changelog -- [Changelog - Leggi le modifiche introdotte.](/CHANGELOG.md) -- [To-Do - Leggi la lista delle funzionalità in sviluppo.](https://github.com/Magnetarman/WinToolkit/blob/Dev/TODO.md) +- 📄 **[Changelog.md - Leggi le modifiche introdotte.](/CHANGELOG.md)** --- @@ -184,15 +187,26 @@ Se WinToolkit ti è stato utile, considera di supportare attivamente il progetto La tua donazione non è solo un ringraziamento, ma un investimento diretto nel futuro e nello sviluppo di questo strumento. -Per effettuare una donazione, clicca sul bottone PayPal qui sotto aggiungendo un riferimento a "WinToolkit" nel messaggio, così entrerai nella Hall of Fame dei Contributori. - -[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/MagnetarManit/10) +Per effettuare una donazione, scopri come cliccand sul pulsante Sponsor in alto a destra. 🚀 Sviluppo continuo: le donazioni mi permettono di dedicare più tempo e risorse per mantenere la versione attuale aggiornata e compatibile, e per implementare nuove e potenti funzionalità. 🏆 Entra nella Hall of Fame: ogni donatore verrà incluso in una nuova sezione dedicata all'interno della lista dei contributori come ringraziamento per il tuo prezioso supporto. -✨ Accesso esclusivo alla futura "GUI Edition" (coming soon): per coprire i costi e il tempo di sviluppo che impiego, la futura e attesissima versione con interfaccia grafica (GUI) sarà facilmente offerta in anteprima a chi avrà supportato il progetto con una donazione libera. La versione da terminale (attualmente disponibile) rimarrà comunque completamente gratuita per tutti. +--- + +## 🏗️ Architettura e sviluppo + +> [!NOTE] +> Questa sezione è rivolta a **contributor e utenti avanzati** che vogliono capire come funziona WinToolkit internamente, come modificarlo o come testare le proprie modifiche prima di aprire una PR. + +WinToolkit usa un sistema di build personalizzato: i sorgenti risiedono nel branch **`Dev`** e vengono compilati automaticamente dalla pipeline CI in un unico file distribuibile su **`main`**. Gli utenti finali clonano `main` e ottengono il toolkit pronto all'uso; i contributor lavorano su `Dev`. + +Per orientarsi nell'architettura del progetto — flusso `Dev` → `main`, compilatore, struttura dei moduli, pipeline CI/CD e istruzioni per testare localmente — leggi: + +📄 **[ARCHITECTURE.md](.github/Docs/ARCHITECTURE.md)** + +--- ### 🔰 Contribuisci @@ -201,7 +215,7 @@ Se non puoi donare, puoi comunque aiutarmi a migliorare WinToolkit attraverso qu ⭐ **Metti una stella**: mettendo una stella al progetto lo farai diventare più popolare su GitHub. > [!WARNING] -> Prima di effettuare Pull Request o aprire issue [PER FAVORE LEGGI ATTENTAMENTE LA GUIDA](https://github.com/Magnetarman/WinToolkit/blob/Dev/.github/Docs/PR_And_PullRequest.md). +> Prima di effettuare Pull Request o aprire issue [PER FAVORE LEGGI ATTENTAMENTE LA GUIDA](/.github/Docs/PR_And_PullRequest.md). 🐛 **[Segnala un problema](https://github.com/Magnetarman/WinToolkit/issues)**: segnala un bug che hai trovato o richiedi nuove funzionalità. diff --git a/WinToolkit.ps1 b/WinToolkit.ps1 index 46fd35f8..ea1cdbff 100644 --- a/WinToolkit.ps1 +++ b/WinToolkit.ps1 @@ -14,95 +14,87 @@ function Read-Host { $oldTreatControlC = [console]::TreatControlCAsInput try { [console]::TreatControlCAsInput = $true } catch {} try { - if ($Prompt) { - Write-Host "${Prompt}: " -NoNewline -ForegroundColor Cyan - } + if ($Prompt) { Write-Host "${Prompt}: " -NoNewline -ForegroundColor Cyan } $inputString = "" while ($true) { - if ([console]::KeyAvailable) { - $keyInfo = [console]::ReadKey($true) - if ($keyInfo.Modifiers -match "Control" -and $keyInfo.Key -eq "C") { - Write-Host "" - return $null - } - if ($keyInfo.Key -eq "Enter") { - Write-Host "" - if ($AsSecureString) { - $secure = New-Object System.Security.SecureString - foreach ($char in $inputString.ToCharArray()) { $secure.AppendChar($char) } - return $secure - } - return $inputString ?? "" + $keyInfo = [console]::ReadKey($true) + if ($keyInfo.Modifiers -match "Control" -and $keyInfo.Key -eq "C") { + Write-Host "" + return $null + } + if ($keyInfo.Key -eq "Enter") { + Write-Host "" + if ($AsSecureString) { + $secure = New-Object System.Security.SecureString + foreach ($char in $inputString.ToCharArray()) { $secure.AppendChar($char) } + return $secure } - if ($keyInfo.Key -eq "Backspace") { - if ($inputString.Length -gt 0) { - $inputString = $inputString.Substring(0, $inputString.Length - 1) - Write-Host "`b `b" -NoNewline - } + return $inputString ?? "" + } + if ($keyInfo.Key -eq "Backspace") { + if ($inputString.Length -gt 0) { + $inputString = $inputString.Substring(0, $inputString.Length - 1) + Write-Host "`b `b" -NoNewline } - else { - if (-not [char]::IsControl($keyInfo.KeyChar)) { - $inputString += $keyInfo.KeyChar - if ($AsSecureString -or $MaskInput) { - Write-Host "*" -NoNewline -ForegroundColor Yellow - } - else { - Write-Host $keyInfo.KeyChar -NoNewline - } - } + } + else { + if (-not [char]::IsControl($keyInfo.KeyChar)) { + $inputString += $keyInfo.KeyChar + if ($AsSecureString -or $MaskInput) { Write-Host "*" -NoNewline -ForegroundColor Yellow } + else { Write-Host $keyInfo.KeyChar -NoNewline } } } - Start-Sleep -Milliseconds 10 } } catch { - if ($Prompt) { - return Microsoft.PowerShell.Utility\Read-Host -Prompt $Prompt - } + if ($Prompt) { return Microsoft.PowerShell.Utility\Read-Host -Prompt $Prompt } return Microsoft.PowerShell.Utility\Read-Host } finally { - try { - [console]::TreatControlCAsInput = $oldTreatControlC - } - catch {} + try { [console]::TreatControlCAsInput = $oldTreatControlC } catch {} } } $ErrorActionPreference = 'Stop' -$Host.UI.RawUI.WindowTitle = "WinToolkit by MagnetarMan" -$ToolkitVersion = "2.5.3 (Build 13)" +try { $Host.UI.RawUI.WindowTitle = "WinToolkit by MagnetarMan" } catch {} +$ToolkitVersion = "2.5.4 (Build 47)" $AppConfig = @{ - URLs = @{ - GitHubAssetBaseUrl = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/" - GitHubAssetDevBaseUrl = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/Dev/asset/" + URLs = @{ + GitHubAssetBaseUrl = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/" + GitHubAssetDevBaseUrl = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/Dev/asset/" OfficeSetup = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/Setup.exe" OfficeBasicConfig = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/Basic.xml" - SaRAInstaller = "https://github.com/Magnetarman/WinToolkit/raw/refs/heads/main/asset/SaRACmd_17_01_2877_000.zip" - AMDInstaller = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/AMD-Autodetect.exe" - NVCleanstall = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/NVCleanstall_1.19.0.exe" - DDUZip = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/DDU.zip" - DirectXWebSetup = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/dxwebsetup.exe" + GetHelpInstaller = "https://aka.ms/SaRA_EnterpriseVersionFiles" + AMDInstaller = "https://drivers.amd.com/drivers/installer/26.10/whql/amd-software-adrenalin-edition-26.5.2-minimalsetup-260513_web.exe" + NVCleanstall = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/NVCleanstall_1.19.0.exe" + DDUZip = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/DDU.zip" + DriverOverridesJson = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/Dev/asset/DriverOverrides.json" + DirectXWebSetup = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/asset/dxwebsetup.exe" BattleNetInstaller = "https://downloader.battle.net/download/getInstallerForGame?os=win&gameProgram=BATTLENET_APP&version=Live" SevenZipOfficial = "https://www.7-zip.org/a/7zr.exe" WingetInstaller = "https://aka.ms/getwinget" VCRedist86 = "https://aka.ms/vs/17/release/vc_redist.x86.exe" VCRedist64 = "https://aka.ms/vs/17/release/vc_redist.x64.exe" } - Paths = @{ - Root = "$env:LOCALAPPDATA\WinToolkit" - Logs = "$env:LOCALAPPDATA\WinToolkit\logs" - Temp = "$env:TEMP\WinToolkit" - Drivers = "$env:LOCALAPPDATA\WinToolkit\Drivers" - OfficeTemp = "$env:LOCALAPPDATA\WinToolkit\Office" - DriverBackupTemp = "$env:TEMP\DriverBackup_Temp" - DriverBackupLogs = "$env:LOCALAPPDATA\WinToolkit\logs" - GamingDirectX = "$env:LOCALAPPDATA\WinToolkit\Directx" - GamingDirectXSetup = "$env:LOCALAPPDATA\WinToolkit\Directx\dxwebsetup.exe" - BattleNetSetup = "$env:TEMP\Battle.net-Setup.exe" - Desktop = [Environment]::GetFolderPath('Desktop') - TempFolder = $env:TEMP - } - Registry = @{ + Paths = @{ + Root = "$env:LOCALAPPDATA\WinToolkit" + Logs = "$env:LOCALAPPDATA\WinToolkit\logs" + Temp = "$env:TEMP\WinToolkit" + Drivers = "$env:LOCALAPPDATA\WinToolkit\Drivers" + OfficeTemp = "$env:LOCALAPPDATA\WinToolkit\Office" + DriverBackupTemp = "$env:TEMP\DriverBackup_Temp" + DriverBackupLogs = "$env:LOCALAPPDATA\WinToolkit\logs" + GamingDirectX = "$env:LOCALAPPDATA\WinToolkit\Directx" + GamingDirectXSetup = "$env:LOCALAPPDATA\WinToolkit\Directx\dxwebsetup.exe" + BattleNetSetup = "$env:TEMP\Battle.net-Setup.exe" + Desktop = [Environment]::GetFolderPath('Desktop') + Startup = [Environment]::GetFolderPath('Startup') + TempFolder = $env:TEMP + LocalAppData = $env:LOCALAPPDATA + System32 = "$env:windir\System32" + SoftwareDistribution = "$env:windir\SoftwareDistribution" + Catroot2 = "$env:windir\System32\catroot2" + } + Registry = @{ WindowsUpdatePolicies = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" ExcludeWUDrivers = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\ExcludeWUDriversInQualityUpdate" OfficeTelemetry = "HKLM:\SOFTWARE\Microsoft\Office\Common\ClientTelemetry" @@ -116,6 +108,15 @@ $AppConfig = @{ StartupRun = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" WindowsTerminal = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" } + WindowsTerminal = @{ + DelegationTerminalClsid = "{E12F0936-0E6F-548E-A9F6-B20C69A27D17}" + DelegationConsoleClsid = "{B23D10C0-31E3-401A-97EF-4BB30B62E10B}" + } + WingetProcesses = @( + 'WinStore.App', 'wsappx', 'AppInstaller', + 'Microsoft.WindowsStore', 'Microsoft.DesktopAppInstaller', + 'winget', 'WindowsPackageManagerServer' + ) } $Global:Spinners = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.ToCharArray() $Global:MsgStyles = @{ @@ -124,28 +125,10 @@ $Global:MsgStyles = @{ Error = @{ Icon = '❌'; Color = 'Red' } Info = @{ Icon = '💎'; Color = 'Cyan' } Progress = @{ Icon = '🔄'; Color = 'Magenta' } + Question = @{ Icon = '❓'; Color = 'Cyan' } } $Global:ExecutionLog = @() $Global:NeedsFinalReboot = $false -function Update-EnvironmentPath { - $machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine') - $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') - $newPath = ($machinePath, $userPath | Where-Object { $_ }) -join ';' - $env:Path = $newPath - [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'Process') -} -function Get-WingetExecutable { - $aliasPath = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\winget.exe" - if (Test-Path $aliasPath) { return $aliasPath } - $arch = [Environment]::Is64BitOperatingSystem ? "x64" : "x86" - $wingetDir = Get-ChildItem -Path "$env:ProgramFiles\WindowsApps" -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | - Sort-Object Name -Descending | Select-Object -First 1 - if ($wingetDir) { - $exe = Join-Path $wingetDir.FullName "winget.exe" - if (Test-Path $exe) { return $exe } - } - return "winget" -} function Clear-ProgressLine { if ($Host.Name -eq 'ConsoleHost') { try { @@ -158,38 +141,55 @@ function Clear-ProgressLine { } } } +function Center-Text { + param([string]$Text, [int]$Width = 0) + if ($Width -eq 0) { $Width = try { $Host.UI.RawUI.BufferSize.Width } catch { 80 } } + $padding = [Math]::Max(0, [Math]::Floor(($Width - $Text.Length) / 2)) + return (' ' * $padding + $Text) +} function Write-StyledMessage { param( - [ValidateSet('Success', 'Warning', 'Error', 'Info', 'Progress')][string]$Type, - [string]$Text + [ValidateSet('Success', 'Warning', 'Error', 'Info', 'Progress', 'Question')][string]$Type, + [string]$Text, + [switch]$NoNewline ) $style = $Global:MsgStyles[$Type] $timestamp = Get-Date -Format "HH:mm:ss" - Write-Host "[$timestamp] $($style.Icon) $Text" -ForegroundColor $style.Color + Write-Host "[$timestamp] $($style.Icon) $Text" -ForegroundColor $style.Color -NoNewline:$NoNewline $logLevel = switch ($Type) { - 'Success' { 'SUCCESS' } - 'Warning' { 'WARNING' } - 'Error' { 'ERROR' } - 'Progress' { 'INFO' } - default { 'INFO' } + 'Success' { 'SUCCESS' } 'Warning' { 'WARNING' } 'Error' { 'ERROR' } default { 'INFO' } } Write-ToolkitLog -Level $logLevel -Message $Text } -function Center-Text { +function Show-ProgressBar { + param([string]$Activity, [string]$Status, [int]$Percent, [string]$Icon = '⏳', [string]$Spinner = '', [string]$Color = 'Green') + $safePercent = [math]::Max(0, [math]::Min(100, $Percent)) + $filled = '█' * [math]::Floor($safePercent * 30 / 100) + $empty = '░' * (30 - $filled.Length) + $bar = "[$filled$empty] {0,3}%" -f $safePercent + if (-not $Global:GuiSessionActive) { + Write-Host "`r$Spinner $Icon $Activity $bar $Status" -NoNewline -ForegroundColor $Color + if ($Percent -ge 100) { Write-Host '' } + } +} +function Write-ProgressUpdate { param( - [string]$Text, - [int]$Width = $Host.UI.RawUI.BufferSize.Width + [string]$Activity, + [string]$Status = '', + [int]$Percent = 0, + [string]$Icon = '⏳', + [string]$Color = 'Green', + [string]$Spinner = '' ) - $padding = [Math]::Max(0, [Math]::Floor(($Width - $Text.Length) / 2)) - return (' ' * $padding + $Text) + if ($Global:GuiSessionActive) { return } + Clear-ProgressLine + Show-ProgressBar -Activity $Activity -Status $Status -Percent $Percent -Icon $Icon -Spinner $Spinner -Color $Color } function Show-Header { param([string]$SubTitle = "Menu Principale") - if ($Global:GuiSessionActive) { - return - } - Clear-Host - $width = $Host.UI.RawUI.BufferSize.Width + if ($Global:GuiSessionActive) { return } + try { Clear-Host } catch {} + $width = try { $Host.UI.RawUI.BufferSize.Width } catch { 80 } $asciiArt = @( ' __ __ _ _ _ ', ' \ \ / / | | | \ | |', @@ -201,27 +201,67 @@ function Show-Header { " Versione $ToolkitVersion" ) Write-Host ('═' * ($width - 1)) -ForegroundColor Green - foreach ($line in $asciiArt) { - Write-Host (Center-Text $line $width) -ForegroundColor White - } + foreach ($line in $asciiArt) { Write-Host (Center-Text $line $width) -ForegroundColor White } Write-Host ('═' * ($width - 1)) -ForegroundColor Green Write-Host '' } +function Show-ConsoleTable { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)][object[]]$Rows, + [Parameter(Mandatory = $true)][hashtable[]]$Columns, + [string]$Title = '' + ) + $widths = @{} + foreach ($col in $Columns) { $widths[$col.Key] = $col.Header.Length } + foreach ($row in $Rows) { + foreach ($col in $Columns) { + $val = if ($row -is [hashtable]) { "$($row[$col.Key])" } else { "$($row.$($col.Key))" } + if ($val.Length -gt $widths[$col.Key]) { $widths[$col.Key] = $val.Length } + } + } + $sep = '+' + (($Columns | ForEach-Object { '-' * ($widths[$_.Key] + 2) }) -join '+') + '+' + if ($Title) { + $totalWidth = $sep.Length + $paddedTitle = " $Title " + $pad = [Math]::Max(0, [Math]::Floor(($totalWidth - $paddedTitle.Length) / 2)) + Write-Host ('=' * $totalWidth) -ForegroundColor Cyan + Write-Host ((' ' * $pad) + $paddedTitle) -ForegroundColor Cyan + Write-Host ('=' * $totalWidth) -ForegroundColor Cyan + } + Write-Host $sep -ForegroundColor DarkGray + $headerLine = '|' + foreach ($col in $Columns) { $headerLine += ' ' + $col.Header.PadRight($widths[$col.Key]) + ' |' } + Write-Host $headerLine -ForegroundColor Cyan + Write-Host $sep -ForegroundColor DarkGray + foreach ($row in $Rows) { + $line = '|' + foreach ($col in $Columns) { + $val = if ($row -is [hashtable]) { "$($row[$col.Key])" } else { "$($row.$($col.Key))" } + $line += ' ' + $val.PadRight($widths[$col.Key]) + ' |' + } + $rowColor = 'White' + $statusKey = ($Columns | Where-Object { $_.Key -eq 'Status' -or $_.Key -eq 'Stato' } | Select-Object -First 1)?.Key + if ($statusKey) { + $statusVal = if ($row -is [hashtable]) { "$($row[$statusKey])" } else { "$($row.$statusKey)" } + if ($statusVal -match '✅|OK|Successo|Completato') { $rowColor = 'Green' } + elseif ($statusVal -match '⚠️|Warning|Parziale') { $rowColor = 'Yellow' } + elseif ($statusVal -match '❌|Errore|Fallito') { $rowColor = 'Red' } + } + Write-Host $line -ForegroundColor $rowColor + } + Write-Host $sep -ForegroundColor DarkGray +} function Start-ToolkitLog { param([string]$ToolName) - try { - Stop-Transcript -ErrorAction SilentlyContinue - } - catch {} + $Global:CurrentToolName = $ToolName + try { Stop-Transcript -ErrorAction SilentlyContinue } catch {} $dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $logdir = $AppConfig.Paths.Logs - if (-not (Test-Path $logdir)) { - New-Item -Path $logdir -ItemType Directory -Force | Out-Null - } + if (-not (Test-Path $logdir)) { New-Item -Path $logdir -ItemType Directory -Force | Out-Null } $Global:CurrentLogFile = "$logdir\${ToolName}_$dateTime.log" $Global:CurrentCorrelationId = [guid]::NewGuid().ToString() $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue - $sys = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue $psVer = $PSVersionTable.PSVersion.ToString() $psEd = $PSVersionTable.PSEdition $psCompat = ($PSVersionTable.PSCompatibleVersions | ForEach-Object { $_.ToString() }) -join ', ' @@ -232,22 +272,12 @@ function Start-ToolkitLog { $build = [int]$os.BuildNumber $verMap = @{26100 = '24H2'; 22631 = '23H2'; 22621 = '22H2'; 22000 = '21H2'; 19045 = '22H2'; 19044 = '21H2' } $dispVer = 'N/A' - foreach ($k in ($verMap.Keys | Sort-Object -Descending)) { - if ($build -ge $k) { - $dispVer = $verMap[$k] - break - } - } + foreach ($k in ($verMap.Keys | Sort-Object -Descending)) { if ($build -ge $k) { $dispVer = $verMap[$k]; break } } $header = @" [START LOG HEADER] Start time : $dateTime CorrelationId : $($Global:CurrentCorrelationId) ToolName : $ToolName -Username : $([Environment]::UserDomainName + '\' + [Environment]::UserName) -RunAs User : $([Security.Principal.WindowsIdentity]::GetCurrent().Name) -Machine : $($sys.Name) ($($os.Caption) $($os.Version)) -Host Application : $([Environment]::CommandLine) -Process ID : $PID PSVersion : $psVer PSEdition : $psEd GitCommitId : $gitId @@ -261,10 +291,7 @@ SerializationVersion : $serVer WSManStackVersion : $wsManVer [END LOG HEADER] "@ - try { - Add-Content -Path $Global:CurrentLogFile -Value $header -Encoding UTF8 -ErrorAction SilentlyContinue - } - catch {} + try { Add-Content -Path $Global:CurrentLogFile -Value $header -Encoding UTF8 -ErrorAction SilentlyContinue } catch {} } function Write-ToolkitLog { param( @@ -276,71 +303,173 @@ function Write-ToolkitLog { if (-not $Global:CurrentLogFile) { return } $ts = Get-Date -Format "HH:mm:ss" $clean = $Message -replace '^\s+', '' + $clean = $clean -replace '\x1B\[[0-9;]*[a-zA-Z]', '' + $clean = $clean -replace '[⌀-⏿☀-➿\uD800-\uDFFF]', '' $line = "[$ts] [$Level] $clean" if ($Context.Count -gt 0) { + try { $line += " | Context: " + ($Context | ConvertTo-Json -Compress -Depth 3) } catch {} + } + try { + $mutex = New-Object System.Threading.Mutex($false, "Global\WinToolkitLogMutex") + $hasHandle = $false try { - $line += " | Context: " + ($Context | ConvertTo-Json -Compress -Depth 3) + $hasHandle = $mutex.WaitOne(5000) + if ($hasHandle) { Add-Content -Path $Global:CurrentLogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue } } - catch {} + finally { + if ($hasHandle) { $mutex.ReleaseMutex() } + $mutex.Dispose() + } + } + catch {} +} +function Write-ToolkitError { + param( + [System.Management.Automation.ErrorRecord]$Record, + [string]$ToolName, + [string]$Message = "Errore critico" + ) + Write-StyledMessage -Type 'Error' -Text "$Message in ${ToolName}: $($Record.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "$Message in $ToolName" -Context @{ + Line = $Record.InvocationInfo.ScriptLineNumber + Exception = $Record.Exception.GetType().FullName + Stack = $Record.ScriptStackTrace + } +} +function Get-SystemInfo { + if ($Global:SystemInfoCache) { return $Global:SystemInfoCache } + try { + $osInfo = Get-CimInstance Win32_OperatingSystem + $computerInfo = Get-CimInstance Win32_ComputerSystem + $diskInfo = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" + $versionMap = @{ + 28000 = "26H1"; 26200 = "25H2"; 26100 = "24H2"; 22631 = "23H2"; 22621 = "22H2"; 22000 = "21H2" + 19045 = "22H2"; 19044 = "21H2"; 19043 = "21H1"; 19042 = "20H2"; 19041 = "2004"; 18363 = "1909" + 18362 = "1903"; 17763 = "1809"; 17134 = "1803"; 16299 = "1709"; 15063 = "1703"; 14393 = "1607" + 10586 = "1511"; 10240 = "1507" + } + $build = [int]$osInfo.BuildNumber + $ver = "N/A" + foreach ($k in ($versionMap.Keys | Sort-Object -Descending)) { if ($build -ge $k) { $ver = $versionMap[$k]; break } } + $Global:SystemInfoCache = @{ + ProductName = $osInfo.Caption -replace 'Microsoft ', '' + BuildNumber = $build + DisplayVersion = $ver + Architecture = $osInfo.OSArchitecture + ComputerName = $computerInfo.Name + TotalRAM = [Math]::Round($computerInfo.TotalPhysicalMemory / 1GB, 2) + TotalDisk = [Math]::Round($diskInfo.Size / 1GB, 0) + FreeDisk = [Math]::Round($diskInfo.FreeSpace / 1GB, 0) + FreePercentage = [Math]::Round(($diskInfo.FreeSpace / $diskInfo.Size) * 100, 0) + } + return $Global:SystemInfoCache } + catch { return $null } +} +function Get-BitlockerStatus { try { - Add-Content -Path $Global:CurrentLogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue + $out = & manage-bde -status C: 2>&1 + if ($out -match "Stato protezione:\s*(.*)") { return $matches[1].Trim() } + return "Non configurato" } - catch {} + catch { return "Disattivato" } +} +function Get-LocalUserProfiles { + return Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notmatch '^(Public|Default|Default User|All Users)$' } +} +function Initialize-ToolkitPaths { + foreach ($path in $AppConfig.Paths.Values) { + if (-not (Test-Path $path -PathType Leaf) -and $path -notmatch "\.exe$|\.zip$|\.msixbundle$") { + try { + if (-not (Test-Path $path)) { $null = New-Item -Path $path -ItemType Directory -Force -ErrorAction SilentlyContinue } + } + catch {} + } + } +} +function Update-EnvironmentPath { + $machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine') + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + $newPath = ($machinePath, $userPath | Where-Object { $_ }) -join ';' + $env:Path = $newPath + [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'Process') +} +function Set-RegistryValue { + param([string]$Path, [string]$Name, $Value, [string]$Type = 'DWord') + if (-not (Test-Path $Path)) { $null = New-Item -Path $Path -Force } + Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force +} +function Stop-ToolkitProcesses { + param([string[]]$ProcessNames) + Write-StyledMessage -Type Info -Text "Chiusura processi interferenti..." + foreach ($procName in $ProcessNames) { + Get-Process -Name $procName -ErrorAction SilentlyContinue | + Where-Object { $_.Id -ne $PID } | + Stop-Process -Force -ErrorAction SilentlyContinue + } + Start-Sleep -Seconds 2 } function Invoke-ExternalCommandWithLog { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] - [string]$Command, - [Parameter(Mandatory = $false)] + [Parameter(Mandatory = $true)][string]$Command, [string[]]$Arguments = @(), - [Parameter(Mandatory = $false)] [string]$WorkingDirectory, - [Parameter(Mandatory = $false)] [int]$TimeoutSeconds = 0, - [Parameter(Mandatory = $false)] - [string]$LogContextKey = '' + [string]$LogContextKey = '', + [string]$Activity = '', + [int]$UpdateInterval = 500, + [string]$Tool = $Global:CurrentToolName, + [string]$Step = 'ExternalCommand' ) $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + $startTime = Get-Date $argString = $Arguments -join ' ' - Write-ToolkitLog -Level 'INFO' -Message "Esecuzione comando esterno: $Command $argString" -Context @{ - Command = $Command - Arguments = $Arguments - WorkingDir = $WorkingDirectory - TimeoutSec = $TimeoutSeconds - ContextKey = $LogContextKey + Write-ToolkitLog -Level 'INFO' -Message "Esecuzione comando: $Command $argString (Timeout: ${TimeoutSeconds}s)" + Write-ToolkitLog -Level 'DEBUG' -Message "Contesto comando" -Context @{ + Tool = $Tool; Step = $Step; WorkingDir = $WorkingDirectory; ContextKey = $LogContextKey } $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = $Command $psi.Arguments = $argString - if ($WorkingDirectory) { - $psi.WorkingDirectory = $WorkingDirectory - } + if ($WorkingDirectory) { $psi.WorkingDirectory = $WorkingDirectory } $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.CreateNoWindow = $true $proc = [System.Diagnostics.Process]::new() $proc.StartInfo = $psi - $outText = "" - $errText = "" - $success = $false - $exitCode = $null + $outText = ""; $errText = ""; $success = $false; $exitCode = $null; $timedOut = $false try { - if (-not $proc.Start()) { - throw "Impossibile avviare il processo esterno." - } + if (-not $proc.Start()) { throw "Impossibile avviare il processo esterno." } $outTask = $proc.StandardOutput.ReadToEndAsync() $errTask = $proc.StandardError.ReadToEndAsync() - if ($TimeoutSeconds -gt 0) { - if (-not $proc.WaitForExit($TimeoutSeconds * 1000)) { + if ($Activity) { + $spinnerIndex = 0; $percent = 0 + while (-not $proc.HasExited -and ($TimeoutSeconds -eq 0 -or ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds)) { + $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] + $elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1) + if ($percent -lt 90) { $percent += Get-Random -Minimum 1 -Maximum 3 } + Write-ProgressUpdate -Activity $Activity -Status "Esecuzione in corso... ($elapsed secondi)" -Percent $percent -Icon '⏳' -Spinner $spinner + Start-Sleep -Milliseconds $UpdateInterval + $proc.Refresh() + } + if (-not $proc.HasExited -and $TimeoutSeconds -gt 0) { try { $proc.Kill() } catch {} throw "Timeout dopo $TimeoutSeconds secondi." } + Write-ProgressUpdate -Activity $Activity -Status 'Completato' -Percent 100 -Icon '✅' + if (-not $Global:GuiSessionActive) { Write-Host "" } } else { - $proc.WaitForExit() + if ($TimeoutSeconds -gt 0) { + if (-not $proc.WaitForExit($TimeoutSeconds * 1000)) { + try { $proc.Kill() } catch {} + throw "Timeout dopo $TimeoutSeconds secondi." + } + } + else { $proc.WaitForExit() } } try { [System.Threading.Tasks.Task]::WaitAll($outTask, $errTask) } catch {} if ($outTask.Status -eq 'RanToCompletion') { $outText = $outTask.Result } @@ -350,45 +479,26 @@ function Invoke-ExternalCommandWithLog { } catch { $exitCode = if ($exitCode -ne $null) { $exitCode } else { -1 } + if ($_.Exception.Message -match 'Timeout') { $timedOut = $true } Write-ToolkitLog -Level 'ERROR' -Message "Eccezione durante esecuzione comando esterno" -Context @{ - Command = $Command - Arguments = $Arguments - WorkingDir = $WorkingDirectory - TimeoutSec = $TimeoutSeconds - ContextKey = $LogContextKey - Exception = $_.Exception.Message - Stack = $_.ScriptStackTrace + Command = $Command; Arguments = $Arguments; WorkingDir = $WorkingDirectory + TimeoutSec = $TimeoutSeconds; ContextKey = $LogContextKey + Exception = $_.Exception.Message; Stack = $_.ScriptStackTrace } } finally { $stopwatch.Stop() - $elapsed = $stopwatch.Elapsed if ($null -eq $outText) { $outText = "" } if ($null -eq $errText) { $errText = "" } $maxLen = 8000 - $outLogged = $outText - $errLogged = $errText - if ($outLogged.Length -gt $maxLen) { - $outLogged = $outLogged.Substring(0, $maxLen) + "`n[...output troncato...]" - } - if ($errLogged.Length -gt $maxLen) { - $errLogged = $errLogged.Substring(0, $maxLen) + "`n[...stderr troncato...]" - } - Write-ToolkitLog -Level 'INFO' -Message "Risultato comando esterno" -Context @{ - Command = $Command - Arguments = $Arguments - WorkingDir = $WorkingDirectory - TimeoutSec = $TimeoutSeconds - ContextKey = $LogContextKey - ExitCode = $exitCode - Success = $success - Elapsed = $elapsed.ToString() - StdOutSnippet = $outLogged - StdErrSnippet = $errLogged - } - if ($proc) { - $proc.Dispose() + $outLogged = if ($outText.Length -gt $maxLen) { $outText.Substring(0, $maxLen) + "`n[...output troncato...]" } else { $outText } + $errLogged = if ($errText.Length -gt $maxLen) { $errText.Substring(0, $maxLen) + "`n[...stderr troncato...]" } else { $errText } + $statusMsg = if ($success) { "Completato con successo" } else { "Completato con errori" } + Write-ToolkitLog -Level 'INFO' -Message "Comando $statusMsg (ExitCode: $exitCode, Durata: $($stopwatch.Elapsed.ToString('hh\:mm\:ss')))" + Write-ToolkitLog -Level 'DEBUG' -Message "Output comando ($Command)" -Context @{ + ContextKey = $LogContextKey; StdOutSnippet = $outLogged; StdErrSnippet = $errLogged } + if ($proc) { $proc.Dispose() } } [pscustomobject]@{ Success = $success @@ -396,42 +506,341 @@ function Invoke-ExternalCommandWithLog { StdOut = $outText StdErr = $errText Elapsed = $stopwatch.Elapsed + TimedOut = $timedOut } } -function Start-AppxSilentProcess { +function Invoke-WithSpinner { + [CmdletBinding()] param( - [string]$AppxPath, - [string]$Flags = '-ForceApplicationShutdown', - [string[]]$DependencyPaths = @() + [Parameter(Mandatory = $true)][string]$Activity, + [scriptblock]$Action, + [int]$TimeoutSeconds = 300, + [int]$UpdateInterval = 500, + [switch]$Process, + [switch]$Job, + [switch]$Timer, + [scriptblock]$PercentUpdate, + [string]$Command, + [string[]]$Arguments = @(), + [string]$LogContextKey = '' ) - $pathParam = ($Flags -match '-Register') ? "" : "-Path '$($AppxPath -replace "'", "''")'" - $depString = "" - if ($DependencyPaths.Count -gt 0) { - $depString = "-DependencyPackagePath " + (($DependencyPaths | ForEach-Object { "'$($_ -replace "'", "''")'" }) -join ", ") - } - $cmd = @" -`$ProgressPreference = 'SilentlyContinue'; -`$ErrorActionPreference = 'SilentlyContinue'; -try { - Add-AppxPackage $pathParam $depString $Flags -ErrorAction Stop | Out-Null -} -catch { - if (`$_.Exception.Message -match '0x80073D06' -or `$_.Exception.Message -match 'versione successiva') { - exit 0 + $startTime = Get-Date + $spinnerIndex = 0 + $percent = 0 + if ($Command) { + return Invoke-ExternalCommandWithLog -Command $Command -Arguments $Arguments ` + -TimeoutSeconds $TimeoutSeconds -Activity $Activity -UpdateInterval $UpdateInterval -LogContextKey $LogContextKey } - if (`$_.Exception.Message -match '0x80073CF9' -or ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem)) { - try { - if ('$pathParam' -eq '') { - exit 1 # Register for manifest not supported via Provisioned + try { + $result = & $Action + if ($Timer) { + $totalSeconds = $TimeoutSeconds + for ($i = $totalSeconds; $i -gt 0; $i--) { + $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] + $percent = if ($PercentUpdate) { & $PercentUpdate } else { [math]::Round((($totalSeconds - $i) / $totalSeconds) * 100) } + Write-ProgressUpdate -Activity "$Activity - $i secondi" -Status '' -Percent $percent -Icon '⏳' -Spinner $spinner -Color 'Yellow' + Start-Sleep -Seconds 1 } - Add-AppxProvisionedPackage -Online -PackagePath '$($AppxPath -replace "'", "''")' $depString -SkipLicense -ErrorAction Stop | Out-Null - exit 0 + if (-not $Global:GuiSessionActive) { Write-Host '' } + return $true } - catch { - exit 1 + elseif ($Process -and $result -and $result.GetType().Name -eq 'Process') { + while (-not $result.HasExited -and ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) { + $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] + $elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1) + $percent = if ($PercentUpdate) { & $PercentUpdate } elseif ($percent -lt 90) { $percent + (Get-Random -Minimum 1 -Maximum 3) } else { $percent } + Write-ProgressUpdate -Activity $Activity -Status "Esecuzione in corso... ($elapsed secondi)" -Percent $percent -Icon '⏳' -Spinner $spinner + Start-Sleep -Milliseconds $UpdateInterval + $result.Refresh() + } + if (-not $result.HasExited) { + Write-ProgressUpdate -Activity $Activity -Status '' -Percent 0 + if (-not $Global:GuiSessionActive) { Write-Host "" } + Write-StyledMessage -Type 'Warning' -Text "Timeout raggiunto dopo $TimeoutSeconds secondi, terminazione processo..." + $result.Kill(); Start-Sleep -Seconds 2 + return @{ Success = $false; TimedOut = $true; ExitCode = -1 } + } + Write-ProgressUpdate -Activity $Activity -Status 'Completato' -Percent 100 -Icon '✅' + if (-not $Global:GuiSessionActive) { Write-Host "" } + return @{ Success = $true; TimedOut = $false; ExitCode = $result.ExitCode } + } + elseif ($Job -and $result -and $result.GetType().Name -eq 'Job') { + while ($result.State -eq 'Running') { + $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] + Write-Host "`r$spinner $Activity..." -NoNewline -ForegroundColor Yellow + Start-Sleep -Milliseconds $UpdateInterval + } + $jobResult = Receive-Job $result -Wait + Write-Host '' + return $jobResult + } + else { + Start-Sleep -Seconds $TimeoutSeconds + return $result } } - exit 1 + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante ${Activity}: $($_.Exception.Message)" + return @{ Success = $false; Error = $_.Exception.Message } + } +} +function Start-InterruptibleCountdown { + param([int]$Seconds = 30, [string]$Message = "Riavvio automatico", [switch]$Suppress) + if ($Suppress) { return $true } + Write-StyledMessage -Type 'Info' -Text '💡 Premi un tasto qualsiasi per annullare...' + Write-Host '' + for ($i = $Seconds; $i -gt 0; $i--) { + if ([Console]::KeyAvailable) { + $null = [Console]::ReadKey($true) + Write-Host "`n" + Write-StyledMessage -Type 'Warning' -Text '⏸️ Riavvio del sistema annullato.' + return $false + } + $percent = [Math]::Round((($Seconds - $i) / $Seconds) * 100) + Write-ProgressUpdate -Activity "$Message tra $i secondi" -Status '' -Percent $percent -Icon '⏰' -Color 'Red' + Start-Sleep 1 + } + Write-Host "`n" + return $true +} +function Start-ToolkitSession { + param([string]$ToolName, [string]$SubTitle = $ToolName) + Start-ToolkitLog -ToolName $ToolName + Show-Header -SubTitle $SubTitle + try { $Host.UI.RawUI.WindowTitle = "$SubTitle By MagnetarMan" } catch {} +} +function Invoke-ToolkitReboot { + param( + [string]$Message = "Operazione completata", + [int]$Seconds = 30, + [switch]$SuppressIndividualReboot + ) + if ($SuppressIndividualReboot) { + $Global:NeedsFinalReboot = $true + Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio individuale soppresso. Verrà gestito un riavvio finale." + } + else { + if (Start-InterruptibleCountdown -Seconds $Seconds -Message $Message) { + Restart-Computer -Force + } + } +} +function Remove-ItemSafely { + param([Parameter(Mandatory = $true)][string]$Path, [switch]$Recurse) + if (-not (Test-Path $Path)) { return $false } + try { + $params = @{ Path = $Path; Force = $true; ErrorAction = 'SilentlyContinue' } + if ($Recurse) { $params['Recurse'] = $true } + Remove-Item @params *>$null + Clear-ProgressLine + return $true + } + catch { return $false } +} +function Invoke-ToolkitDownload { + param( + [string]$Uri, + [string]$OutputPath, + [string]$Description = "file", + [int]$MaxRetries = 3, + [switch]$NoSpinner + ) + for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { + try { + Write-StyledMessage -Type 'Info' -Text "📥 Download $Description..." + $parentDir = Split-Path -Parent $OutputPath + if (-not (Test-Path $parentDir)) { + New-Item -Path $parentDir -ItemType Directory -Force | Out-Null + } + $handler = New-Object System.Net.Http.HttpClientHandler + $handler.AllowAutoRedirect = $true + $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate + $httpClient = New-Object System.Net.Http.HttpClient($handler) + $httpClient.Timeout = [TimeSpan]::FromSeconds(300) + $httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + if ($Uri -match 'drivers\.amd\.com|amd-software') { + $httpClient.DefaultRequestHeaders.Add("Referer", "https://www.amd.com") + } + $totalBytes = 0 + try { + $headRequest = New-Object System.Net.Http.HttpRequestMessage([System.Net.Http.HttpMethod]::Head, $Uri) + $headResponse = $httpClient.SendAsync($headRequest).Result + if ($headResponse.Content.Headers.ContentLength -gt 0) { + $totalBytes = $headResponse.Content.Headers.ContentLength + } + $headResponse.Dispose() + } + catch {} + $getRequest = New-Object System.Net.Http.HttpRequestMessage([System.Net.Http.HttpMethod]::Get, $Uri) + $getResponse = $httpClient.SendAsync($getRequest, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result + if (-not $getResponse.IsSuccessStatusCode) { + throw "HTTP Error $($getResponse.StatusCode): $($getResponse.ReasonPhrase)" + } + if ($totalBytes -eq 0 -and $getResponse.Content.Headers.ContentLength -gt 0) { + $totalBytes = $getResponse.Content.Headers.ContentLength + } + $isUnknownSize = ($totalBytes -eq 0) + $fakeProgressStart = $null + if ($isUnknownSize -and -not $Global:GuiSessionActive) { + $fakeProgressStart = Get-Date + Write-ProgressUpdate -Activity "Download $Description" ` + -Status "Avvio download in corso..." ` + -Percent 8 -Icon '📥' -Color 'Cyan' + Start-Sleep -Milliseconds 120 + } + $contentStream = $getResponse.Content.ReadAsStreamAsync().Result + $fileStream = [System.IO.File]::Create($OutputPath) + $buffer = New-Object byte[] 8192 + $totalRead = 0 + $lastPercent = -1 + $lastProgressTime = Get-Date + try { + while ($true) { + $read = $contentStream.Read($buffer, 0, $buffer.Length) + if ($read -eq 0) { break } + $fileStream.Write($buffer, 0, $read) + $totalRead += $read + if (-not $Global:GuiSessionActive) { + $currentDisplay = if ($totalRead -gt 1048576) { + "$([Math]::Round($totalRead / 1048576, 1)) MB" + } + else { + "$([Math]::Round($totalRead / 1024, 1)) KB" + } + if ($totalBytes -gt 0) { + $percent = [Math]::Round(($totalRead / $totalBytes) * 100) + $totalDisplay = if ($totalBytes -gt 1048576) { + "$([Math]::Round($totalBytes / 1048576, 1)) MB" + } + else { + "$([Math]::Round($totalBytes / 1024, 1)) KB" + } + $status = "($currentDisplay / $totalDisplay)" + $icon = '📥' + $col = 'Cyan' + } + else { + if ($fakeProgressStart) { + $elapsed = ((Get-Date) - $fakeProgressStart).TotalSeconds + $percent = [math]::Min(95, [math]::Floor(8 + ($elapsed * 1.52))) + } + else { + $percent = 50 + } + $status = "$currentDisplay scaricati" + $icon = '📥' + $col = 'Cyan' + } + $now = Get-Date + $timeSinceLast = ($now - $lastProgressTime).TotalMilliseconds + $shouldUpdate = $false + if ($lastPercent -eq -1) { + $shouldUpdate = $true + } + elseif ($totalBytes -gt 0) { + if ($percent -ne $lastPercent -and $timeSinceLast -gt 250) { + $shouldUpdate = $true + } + } + else { + if ($timeSinceLast -gt 400 -or $percent -ne $lastPercent) { + $shouldUpdate = $true + } + } + if ($shouldUpdate) { + Write-ProgressUpdate -Activity "Download $Description" -Status $status -Percent $percent -Icon $icon -Color $col + $lastPercent = $percent + $lastProgressTime = $now + } + } + } + } + finally { + $fileStream.Dispose() + $contentStream.Dispose() + } + $httpClient.Dispose() + $handler.Dispose() + if (Test-Path $OutputPath) { + if ($totalBytes -gt 0) { + Write-ProgressUpdate -Activity "Download $Description" -Status 'Completato' -Percent 100 -Icon '✅' -Color 'Green' + } + else { + Write-ProgressUpdate -Activity "Download $Description" -Status 'Completato' -Percent 100 -Icon '✅' -Color 'Green' + if (-not $Global:GuiSessionActive) { Write-Host "" } + } + Write-StyledMessage -Type 'Success' -Text "Download completato: $Description." + return $true + } + } + catch { + if (Test-Path $OutputPath) { + Remove-Item $OutputPath -Force -ErrorAction SilentlyContinue + } + if ($attempt -lt $MaxRetries) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ Tentativo $attempt/$MaxRetries fallito: $($_.Exception.Message). Riprovo..." + Start-Sleep -Seconds 2 + } + } + } + Write-StyledMessage -Type 'Error' -Text "❌ Download fallito dopo $MaxRetries tentativi: $Description." + return $false +} +function Restart-ServiceSafely { + param([string]$Name, [int]$WaitSeconds = 1) + try { + Stop-Service -Name $Name -Force -ErrorAction Stop + Start-Sleep -Seconds $WaitSeconds + Start-Service -Name $Name -ErrorAction Stop + Write-StyledMessage -Type 'Success' -Text "Servizio riavviato: $Name." + return $true + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Impossibile riavviare '$Name': $($_.Exception.Message)." + return $false + } +} +function Get-WingetExecutable { + $aliasPath = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\winget.exe" + if (Test-Path $aliasPath) { return $aliasPath } + $arch = [Environment]::Is64BitOperatingSystem ? "x64" : "x86" + $wingetDir = Get-ChildItem -Path "$env:ProgramFiles\WindowsApps" ` + -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | Select-Object -First 1 + if ($wingetDir) { + $exe = Join-Path $wingetDir.FullName "winget.exe" + if (Test-Path $exe) { return $exe } + } + return "winget" +} +function Start-AppxSilentProcess { + param( + [string]$AppxPath, + [string]$Flags = '-ForceApplicationShutdown', + [string[]]$DependencyPaths = @() + ) + $pathParam = ($Flags -match '-Register') ? "" : "-Path '$($AppxPath -replace "'", "''")'" + $depString = "" + if ($DependencyPaths.Count -gt 0) { + $depString = "-DependencyPackagePath " + (($DependencyPaths | ForEach-Object { "'$($_ -replace "'", "''")'" }) -join ", ") + } + $cmd = @" +`$ProgressPreference = 'SilentlyContinue'; +`$ErrorActionPreference = 'SilentlyContinue'; +try { + Add-AppxPackage $pathParam $depString $Flags -ErrorAction Stop | Out-Null +} +catch { + if (`$_.Exception.Message -match '0x80073D06' -or `$_.Exception.Message -match 'versione successiva') { exit 0 } + if (`$_.Exception.Message -match '0x80073CF9' -or ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem)) { + try { + if ('$pathParam' -eq '') { exit 1 } + Add-AppxProvisionedPackage -Online -PackagePath '$($AppxPath -replace "'", "''")' $depString -SkipLicense -ErrorAction Stop | Out-Null + exit 0 + } + catch { exit 1 } + } + exit 1 } exit 0 "@ @@ -447,10 +856,7 @@ exit 0 return [System.Diagnostics.Process]::Start($psi) } function Wait-WingetReady { - param( - [int]$MaxWaitSeconds = 300, - [int]$PollIntervalSeconds = 5 - ) + param([int]$MaxWaitSeconds = 300, [int]$PollIntervalSeconds = 5) Write-StyledMessage -Type Info -Text "🔍 Validazione integrità Winget in corso (timeout: $MaxWaitSeconds s)..." $wingetExe = Get-WingetExecutable $maxRetries = [Math]::Floor($MaxWaitSeconds / $PollIntervalSeconds) @@ -466,7 +872,7 @@ function Wait-WingetReady { return $true } } - catch { } + catch {} $remaining = $MaxWaitSeconds - ($i * $PollIntervalSeconds) Write-StyledMessage -Type Progress -Text "⏳ Winget non ancora pronto (tentativo $i/$maxRetries, restano $remaining s). Attesa..." Start-Sleep -Seconds $PollIntervalSeconds @@ -489,16 +895,18 @@ function Reset-Winget { $dllPath = [string]::Format('{0}\system32\concrt140.dll', $env:windir) return (Test-Path $registryPath) -and ($major -ge 14) -and (Test-Path $dllPath) } - function _Invoke-ForceClose { - Write-StyledMessage -Type Info -Text "Chiusura processi interferenti..." - $procs = @("WinStore.App", "wsappx", "AppInstaller", "Microsoft.WindowsStore", - "Microsoft.DesktopAppInstaller", "winget", "WindowsPackageManagerServer") - foreach ($p in $procs) { - Get-Process -Name $p -ErrorAction SilentlyContinue | - Where-Object { $_.Id -ne $PID } | - Stop-Process -Force -ErrorAction SilentlyContinue + function _Register-AppxManifest { + try { + $manifest = (Get-AppxPackage -Name 'Microsoft.DesktopAppInstaller' -ErrorAction SilentlyContinue).InstallLocation + if ($manifest) { + $manifestXml = Join-Path $manifest 'AppxManifest.xml' + if (Test-Path $manifestXml) { + Write-StyledMessage -Type Info -Text "Re-registrazione manifest: AppxManifest.xml previene leak." + Start-AppxSilentProcess -AppxPath $manifestXml -Flags '-DisableDevelopmentMode -Register -ForceApplicationShutdown' | Out-Null + } + } } - Start-Sleep 2 + catch {} } function _Get-LatestAssetUrl { param([string]$Match) @@ -540,9 +948,7 @@ function Reset-Winget { function _Test-PathInEnvironment { param([string]$PathToCheck, [string]$Scope = 'Both') $found = $false - if ($Scope -in 'User', 'Both') { - if (($env:PATH -split ';').Contains($PathToCheck)) { $found = $true } - } + if ($Scope -in 'User', 'Both') { if (($env:PATH -split ';').Contains($PathToCheck)) { $found = $true } } if ($Scope -in 'System', 'Both') { $syspath = [Environment]::GetEnvironmentVariable('PATH', 'Machine') if (($syspath -split ';').Contains($PathToCheck)) { $found = $true } @@ -576,18 +982,15 @@ function Reset-Winget { Set-Acl -Path $FolderPath -AclObject $acl -ErrorAction Stop Write-StyledMessage -Type Info -Text "Permessi cartella aggiornati: $FolderPath." } - catch { - Write-StyledMessage -Type Warning -Text "Impossibile impostare permessi su '$FolderPath': $($_.Exception.Message)." - } + catch { Write-StyledMessage -Type Warning -Text "Impossibile impostare permessi su '$FolderPath': $($_.Exception.Message)." } } function _Set-WingetPathPermissions { $wingetFolderPath = $null try { $arch = [Environment]::Is64BitOperatingSystem ? 'x64' : 'x86' $wingetDir = Get-ChildItem "$env:ProgramFiles\WindowsApps" ` - -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" ` - -ErrorAction SilentlyContinue | - Sort-Object Name -Descending | Select-Object -First 1 + -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | Select-Object -First 1 if ($wingetDir) { $wingetFolderPath = $wingetDir.FullName } } catch {} @@ -601,23 +1004,16 @@ function Reset-Winget { function _Repair-WingetDatabase { Write-StyledMessage -Type Info -Text "🔧 Ripristino database Winget." try { - $procs = @("WinStore.App", "wsappx", "AppInstaller", "Microsoft.WindowsStore", - "Microsoft.DesktopAppInstaller", "winget", "WindowsPackageManagerServer") - foreach ($p in $procs) { - Get-Process -Name $p -ErrorAction SilentlyContinue | - Where-Object { $_.Id -ne $PID } | - Stop-Process -Force -ErrorAction SilentlyContinue - } - Start-Sleep 2 + Stop-ToolkitProcesses -ProcessNames $AppConfig.WingetProcesses $cachePath = "$env:LOCALAPPDATA\WinGet" if (Test-Path $cachePath) { Write-StyledMessage -Type Info -Text "Pulizia cache Winget." Get-ChildItem -Path $cachePath -Recurse -Force -ErrorAction SilentlyContinue | - Where-Object { $_.FullName -notmatch '\\lock\\|\\tmp\\' } | - ForEach-Object { try { Remove-Item $_.FullName -Force -Recurse -ErrorAction SilentlyContinue } catch {} } + Where-Object { $_.FullName -notmatch '\\lock\\|\\tmp\\' } | + ForEach-Object { try { Remove-Item $_.FullName -Force -Recurse -ErrorAction SilentlyContinue } catch {} } } @("$env:LOCALAPPDATA\WinGet\Data\USERTEMPLATE.json", - "$env:LOCALAPPDATA\WinGet\Data\DEFAULTUSER.json") | ForEach-Object { + "$env:LOCALAPPDATA\WinGet\Data\DEFAULTUSER.json") | ForEach-Object { if (Test-Path $_ -PathType Leaf) { Write-StyledMessage -Type Info -Text "Reset file stato: $_." Remove-Item $_ -Force -ErrorAction SilentlyContinue @@ -636,7 +1032,8 @@ function Reset-Winget { Start-AppxSilentProcess -AppxPath $manifestXml -Flags '-DisableDevelopmentMode -Register -ForceApplicationShutdown' | Out-Null } } - } catch { } + } + catch {} try { if (Get-Command Repair-WinGetPackageManager -ErrorAction SilentlyContinue) { Write-StyledMessage -Type Info -Text "Esecuzione Repair-WinGetPackageManager." @@ -647,9 +1044,7 @@ function Reset-Winget { if ($_.Exception.Message -match '0x80073D06' -or $_.Exception.Message -match 'versione successiva') { Write-StyledMessage -Type Success -Text "Repair-WinGetPackageManager completato (versione superiore già presente)." } - else { - Write-StyledMessage -Type Warning -Text "Repair-WinGetPackageManager fallito: $($_.Exception.Message)." - } + else { Write-StyledMessage -Type Warning -Text "Repair-WinGetPackageManager fallito: $($_.Exception.Message)." } } _Set-WingetPathPermissions Update-EnvironmentPath @@ -665,9 +1060,7 @@ function Reset-Winget { try { if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { if ($PSVersionTable.PSVersion.Major -lt 7) { - try { - Install-PackageProvider -Name 'NuGet' -Force -ForceBootstrap -ErrorAction SilentlyContinue *>$null - } + try { Install-PackageProvider -Name 'NuGet' -Force -ForceBootstrap -ErrorAction SilentlyContinue *>$null } catch { Write-StyledMessage -Type Warning -Text "NuGet provider non installabile." } } } @@ -678,9 +1071,7 @@ function Reset-Winget { Import-Module Microsoft.WinGet.Client -ErrorAction SilentlyContinue Write-StyledMessage -Type Success -Text "Modulo WinGet Client installato." } - catch { - Write-StyledMessage -Type Warning -Text "Impossibile installare modulo WinGet Client: $($_.Exception.Message)." - } + catch { Write-StyledMessage -Type Warning -Text "Impossibile installare modulo WinGet Client: $($_.Exception.Message)." } if (Get-Command Repair-WinGetPackageManager -ErrorAction SilentlyContinue) { Write-StyledMessage -Type Info -Text "Tentativo Repair-WinGetPackageManager." try { @@ -690,9 +1081,8 @@ function Reset-Winget { catch { if ($_.Exception.Message -match '0x80073D06' -or $_.Exception.Message -match 'versione successiva') { Write-StyledMessage -Type Success -Text "Repair-WinGetPackageManager ignorato (versione superiore già presente)." - } else { - Write-StyledMessage -Type Warning -Text "Repair-WinGetPackageManager fallito: $($_.Exception.Message)." } + else { Write-StyledMessage -Type Warning -Text "Repair-WinGetPackageManager fallito: $($_.Exception.Message)." } } Start-Sleep 3 } @@ -754,7 +1144,7 @@ function Reset-Winget { Write-StyledMessage -Type Success -Text "✅ Winget già operativo. Nessuna riparazione necessaria." return $true } - _Invoke-ForceClose + Stop-ToolkitProcesses -ProcessNames $AppConfig.WingetProcesses try { Write-StyledMessage -Type Info -Text "⚡ Fase 1: Ripristino Core (VC++, dipendenze AppX, MSIXBundle)." if (-not (_Test-VCRedistInstalled) -or $Force) { @@ -777,11 +1167,8 @@ function Reset-Winget { $archPattern = [Environment]::Is64BitOperatingSystem ? "x64|ne" : "x86|ne" $script:WingetDependencies = @() Get-ChildItem $depDir -Recurse -Filter "*.appx" | - Where-Object { $_.Name -match $archPattern } | - ForEach-Object { - Write-StyledMessage -Type Info -Text "Trovata dipendenza: $($_.Name)." - $script:WingetDependencies += $_.FullName - } + Where-Object { $_.Name -match $archPattern } | + ForEach-Object { Write-StyledMessage -Type Info -Text "Trovata dipendenza: $($_.Name)."; $script:WingetDependencies += $_.FullName } Write-StyledMessage -Type Success -Text "Dipendenze caricate." } Write-StyledMessage -Type Info -Text "Installazione Winget MSIXBundle (con dipendenze)..." @@ -793,16 +1180,7 @@ function Reset-Winget { Start-AppxSilentProcess -AppxPath $bundleFile -DependencyPaths $deps -Flags '-ForceApplicationShutdown' Write-StyledMessage -Type Success -Text "Winget Core installato." } - try { - $manifest = (Get-AppxPackage -Name 'Microsoft.DesktopAppInstaller' -ErrorAction SilentlyContinue).InstallLocation - if ($manifest) { - $manifestXml = Join-Path $manifest 'AppxManifest.xml' - if (Test-Path $manifestXml) { - Start-AppxSilentProcess -AppxPath $manifestXml -Flags '-DisableDevelopmentMode -Register -ForceApplicationShutdown' - } - } - } - catch { } + _Register-AppxManifest Update-EnvironmentPath if (_Test-WingetFunctionality) { Write-StyledMessage -Type Success -Text "✅ Fase 1 completata. Winget operativo." @@ -815,18 +1193,17 @@ function Reset-Winget { } Start-Sleep -Seconds 3 try { - $wingetExeForReset = Get-WingetExecutable - Start-Process -FilePath $wingetExeForReset -ArgumentList 'source', 'reset', '--force' ` + Start-Process -FilePath (Get-WingetExecutable) -ArgumentList 'source', 'reset', '--force' ` -Wait -WindowStyle Hidden -ErrorAction SilentlyContinue } - catch { } + catch {} $deepOk = _Test-WingetDeepValidation if ($deepOk) { Write-StyledMessage -Type Success -Text "✅ Winget ripristinato e testato con successo." return $true } else { - Write-StyledMessage -Type Warning -Text "⚠️ Winget installato. La validazione profonda ha rilevato anomalie (possibili problemi di rete o DB)." + Write-StyledMessage -Type Warning -Text "⚠️ Winget installato. Validazione profonda con anomalie (possibili problemi di rete o DB)." return $true } } @@ -838,208 +1215,323 @@ function Reset-Winget { if (Test-Path $AppConfig.Paths.Temp) { Remove-Item $AppConfig.Paths.Temp -Recurse -Force -ErrorAction SilentlyContinue } } } -function Show-ProgressBar { - param([string]$Activity, [string]$Status, [int]$Percent, [string]$Icon = '⏳', [string]$Spinner = '', [string]$Color = 'Green') - $safePercent = [math]::Max(0, [math]::Min(100, $Percent)) - $filled = '█' * [math]::Floor($safePercent * 30 / 100) - $empty = '▒' * (30 - $filled.Length) - $bar = "[$filled$empty] {0,3}%" -f $safePercent - if (-not $Global:GuiSessionActive) { - Write-Host "`r$Spinner $Icon $Activity $bar $Status" -NoNewline -ForegroundColor $Color - if ($Percent -ge 100) { Write-Host '' } +function Get-UserConfirmation { + param( + [Parameter(Mandatory = $true)][string]$Prompt, + [switch]$DefaultYes, + [ValidateSet('Info', 'Warning', 'Question')][string]$Level = 'Question' + ) + $choices = if ($DefaultYes) { "[S/n]" } else { "[s/N]" } + $fullPrompt = "$Prompt $choices" + if ($Global:GuiSessionActive) { + Write-StyledMessage -Type $Level -Text $fullPrompt + return $true } + Write-StyledMessage -Type $Level -Text "${fullPrompt}: " -NoNewline + $response = Read-Host + Write-ToolkitLog -Level 'INFO' -Message "User Confirmation Prompt: $Prompt | Response: $response" + if ([string]::IsNullOrWhiteSpace($response)) { return $DefaultYes } + return $response -match '^[sS]' } -function Invoke-WithSpinner { - [CmdletBinding()] +function Read-ValidatedChoice { param( - [Parameter(Mandatory = $true)] - [string]$Activity, - [Parameter(Mandatory = $true)] - [scriptblock]$Action, - [Parameter(Mandatory = $false)] - [int]$TimeoutSeconds = 300, - [Parameter(Mandatory = $false)] - [int]$UpdateInterval = 500, - [Parameter(Mandatory = $false)] - [switch]$Process, - [Parameter(Mandatory = $false)] - [switch]$Job, - [Parameter(Mandatory = $false)] - [switch]$Timer, - [Parameter(Mandatory = $false)] - [scriptblock]$PercentUpdate + [int[]]$ValidRange, + [int]$Min, + [int]$Max, + [switch]$AllowZero, + [string]$Prompt = "Seleziona un'opzione", + [string]$RawInput ) - $startTime = Get-Date - $spinnerIndex = 0 - $percent = 0 - try { - $result = & $Action - if ($Timer) { - $totalSeconds = $TimeoutSeconds - for ($i = $totalSeconds; $i -gt 0; $i--) { - $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] - $elapsed = $totalSeconds - $i - if ($PercentUpdate) { - $percent = & $PercentUpdate - } + $currentInput = if ($PSBoundParameters.ContainsKey('RawInput')) { $RawInput } else { $null } + while ($true) { + $input = if ($null -ne $currentInput) { + $val = $currentInput; $currentInput = $null; $val + } + else { + Write-StyledMessage -Type 'Question' -Text "${Prompt}: " -NoNewline + Microsoft.PowerShell.Utility\Read-Host + } + if ([string]::IsNullOrWhiteSpace($input)) { + Write-StyledMessage -Type Warning -Text "⚠️ Input vuoto. Riprova." + continue + } + $choices = $input -split '[\s,]+' | Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ } + if ($choices.Count -gt 0) { + $isValid = $true + foreach ($c in $choices) { + if ($null -ne $ValidRange) { if ($c -notin $ValidRange) { $isValid = $false; break } } else { - $percent = [math]::Round((($totalSeconds - $i) / $totalSeconds) * 100) - } - if (-not $Global:GuiSessionActive) { - Write-Host "`r$spinner ⏳ $Activity - $i secondi..." -NoNewline -ForegroundColor Yellow + if ($AllowZero -and $c -eq 0) { continue } + if ($null -ne $Min -and $c -lt $Min) { $isValid = $false; break } + if ($null -ne $Max -and $c -gt $Max) { $isValid = $false; break } } - Start-Sleep -Seconds 1 } - if (-not $Global:GuiSessionActive) { Write-Host '' } - return $true + if ($isValid) { + Write-ToolkitLog -Level 'INFO' -Message "User Choices: $($choices -join ',')" + return $choices + } } - elseif ($Process -and $result -and $result.GetType().Name -eq 'Process') { - while (-not $result.HasExited -and ((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) { - $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] - $elapsed = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 1) - if ($PercentUpdate) { - $percent = & $PercentUpdate - } - elseif ($percent -lt 90) { - $percent += Get-Random -Minimum 1 -Maximum 3 - } - if (-not $Global:GuiSessionActive) { - Write-Host "`r" -NoNewline + $rangeStr = if ($null -ne $ValidRange) { "$($ValidRange[0]) e $($ValidRange[-1])" } else { "$Min e $Max" } + Write-StyledMessage -Type Warning -Text "⚠️ Scelta non valida. Inserisci numeri compresi tra $rangeStr." + } +} +function WinOSCheck { + if ($Global:GuiSessionActive) { return } + Show-Header -SubTitle "System Check" + $si = Get-SystemInfo + if (-not $si) { Write-StyledMessage -Type 'Warning' -Text "Info sistema non disponibili."; return } + Write-StyledMessage -Type 'Info' -Text "Sistema: $($si.ProductName) ($($si.DisplayVersion))" + if ($si.BuildNumber -ge 22000) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win11/10 recente)." } + elseif ($si.BuildNumber -ge 17763) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win10)." } + elseif ($si.BuildNumber -eq 9600) { Write-StyledMessage -Type 'Warning' -Text "Windows 8.1: Compatibilità parziale." } + else { + Write-StyledMessage -Type 'Error' -Text "$(Center-Text '🤣 ERRORE CRITICO 🤣' 65)" + Write-StyledMessage -Type 'Error' -Text "Davvero pensi che questo script possa fare qualcosa per questa versione?" + Write-Host " Vuoi rischiare? [Y/N]" -ForegroundColor Yellow + if ((Read-Host) -notmatch '^[Yy]$') { exit } + } + Start-Sleep -Seconds 2 +} +function Test-WindowsUpdateStatus { + try { + if ($Global:GuiSessionActive) { return } + Write-StyledMessage -Type 'Info' -Text "🔍 Controllo stato aggiornamenti Windows..." + $pendingReboot = $false + $installerRunning = $false + if (Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue) { + Import-Module PSWindowsUpdate -ErrorAction SilentlyContinue + try { + $rebootStatus = Get-WURebootStatus -ErrorAction SilentlyContinue + if ($rebootStatus -and $rebootStatus.RebootRequired) { + $pendingReboot = $true + Write-StyledMessage -Type 'Warning' -Text "⚠️ Rilevato riavvio pendente per aggiornamenti Windows" } - Show-ProgressBar -Activity $Activity -Status "Esecuzione in corso... ($elapsed secondi)" -Percent $percent -Icon '⏳' -Spinner $spinner - Start-Sleep -Milliseconds $UpdateInterval - $result.Refresh() - } - if (-not $result.HasExited) { - if (-not $Global:GuiSessionActive) { Write-Host "" } - Write-StyledMessage -Type 'Warning' -Text "Timeout raggiunto dopo $TimeoutSeconds secondi, terminazione processo..." - $result.Kill() - Start-Sleep -Seconds 2 - return @{ Success = $false; TimedOut = $true; ExitCode = -1 } - } - if (-not $Global:GuiSessionActive) { - Clear-ProgressLine } - Show-ProgressBar -Activity $Activity -Status 'Completato' -Percent 100 -Icon '✅' - if (-not $Global:GuiSessionActive) { Write-Host "" } - return @{ Success = $true; TimedOut = $false; ExitCode = $result.ExitCode } - } - elseif ($Job -and $result -and $result.GetType().Name -eq 'Job') { - while ($result.State -eq 'Running') { - $spinner = $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] - Write-Host "`r$spinner $Activity..." -NoNewline -ForegroundColor Yellow - Start-Sleep -Milliseconds $UpdateInterval + catch {} + try { + $installerStatus = Get-WUInstallerStatus -ErrorAction SilentlyContinue + if ($installerStatus -and $installerStatus.IsBusy) { + $installerRunning = $true + Write-StyledMessage -Type 'Warning' -Text "⚠️ Servizio installazione aggiornamenti Windows attualmente in esecuzione" + } } - $jobResult = Receive-Job $result -Wait - Write-Host '' - return $jobResult + catch {} } else { - Start-Sleep -Seconds $TimeoutSeconds - return $result + $regPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired", + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired", + "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" + ) + foreach ($path in $regPaths) { + if (Test-Path $path -ErrorAction SilentlyContinue) { $pendingReboot = $true; break } + } + $trustedInstaller = Get-Service -Name TrustedInstaller -ErrorAction SilentlyContinue + if ($trustedInstaller -and $trustedInstaller.Status -eq 'Running') { $installerRunning = $true } } - } + if ($pendingReboot -or $installerRunning) { + $width = try { $Host.UI.RawUI.BufferSize.Width } catch { 80 } + Write-Host "" + Write-Host ('═' * ($width - 1)) -ForegroundColor Yellow + Write-Host "" + Write-Host (Center-Text "⚠️ AVVISO IMPORTANTE ⚠️") -ForegroundColor Yellow + Write-Host "" + Write-Host " Sono stati rilevati aggiornamenti di sistema pendenti:" -ForegroundColor Yellow + if ($pendingReboot) { Write-Host " ✓ Riavvio del sistema richiesto per completare aggiornamenti" -ForegroundColor Yellow } + if ($installerRunning) { Write-Host " ✓ Servizio installazione aggiornamenti Windows in corso" -ForegroundColor Yellow } + Write-Host "" + Write-Host " Questo potrebbe causare malfunzionamenti, errori o comportamenti" -ForegroundColor Yellow + Write-Host " imprevisti in alcune o tutte le funzionalità di WinToolkit." -ForegroundColor Yellow + Write-Host "" + Write-Host (Center-Text "⚠️ PROCEDERE CON CAUTELA ⚠️") -ForegroundColor Red + Write-Host "" + Write-Host " Si consiglia vivamente di completare tutti gli aggiornamenti in corso," -ForegroundColor Yellow + Write-Host " riavviare il sistema e poi riavviare WinToolkit prima di proseguire." -ForegroundColor Yellow + Write-Host "" + Write-Host ('═' * ($width - 1)) -ForegroundColor Yellow + Write-Host "" + Start-Sleep -Seconds 5 + } + else { + Write-StyledMessage -Type 'Success' -Text "✅ Nessun aggiornamento pendente rilevato" + } + } catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante $Activity`: $($_.Exception.Message)" - return @{ Success = $false; Error = $_.Exception.Message } + Write-StyledMessage -Type 'Warning' -Text "⚠️ Impossibile verificare stato aggiornamenti Windows: $($_.Exception.Message)" } } -function Start-InterruptibleCountdown { +function Invoke-OfficeSilentRemoval { + param([Parameter(Mandatory = $true)][string]$Path, [switch]$Recurse) + return Remove-ItemSafely -Path $Path -Recurse:$Recurse +} +function Stop-OfficeProcesses { + $processes = @('winword', 'excel', 'powerpnt', 'outlook', 'onenote', 'msaccess', 'visio', 'lync') + $closed = 0 + Write-StyledMessage -Type 'Info' -Text "📋 Chiusura processi Office." + foreach ($processName in $processes) { + $running = Get-Process -Name $processName -ErrorAction SilentlyContinue + if ($running) { + try { $running | Stop-Process -Force -ErrorAction Stop; $closed++ } + catch { Write-StyledMessage -Type 'Warning' -Text "Impossibile chiudere: $processName." } + } + } + if ($closed -gt 0) { Write-StyledMessage -Type 'Success' -Text "$closed processi Office chiusi." } +} +function Invoke-OfficeDownloadFile([string]$Url, [string]$OutputPath, [string]$Description) { + return Invoke-ToolkitDownload -Uri $Url -OutputPath $OutputPath -Description $Description +} +function Set-OfficePostConfig { + Write-StyledMessage -Type 'Info' -Text "⚙️ Ottimizzazione profonda di Microsoft Office." + $registrySettings = @( + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 }, + @{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "disconnectedstate"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "usercontentdisabled"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "downloadcontentdisabled"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\General"; Name = "ShownOptIn"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\Feedback"; Name = "Enabled"; Value = 0 }, + @{ Path = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\Graphics"; Name = "DisableAnimations"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\Graphics"; Name = "DisableHardwareAcceleration"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\General"; Name = "DisableBootToStartScreen"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\LinkedIn"; Name = "ShowLinkedInIntegration"; Value = 0 } + ) + foreach ($reg in $registrySettings) { + if (-not (Test-Path $reg.Path)) { $null = New-Item -Path $reg.Path -Force } + Set-ItemProperty -Path $reg.Path -Name $reg.Name -Value $reg.Value -Type 'DWord' -Force + } + $tasksToDisable = @( + "OfficeTelemetryAgentLogon", "OfficeTelemetryAgentFallback", + "OfficeBackgroundTaskHandlerRegistration", "OfficeBackgroundTaskHandlerLogon", + "OfficeFeatureUpdates", "OfficeFeatureUpdatesLogon" + ) + foreach ($tName in $tasksToDisable) { + Get-ScheduledTask | Where-Object { $_.TaskName -eq $tName } | Disable-ScheduledTask -ErrorAction SilentlyContinue + } + Write-StyledMessage -Type 'Success' -Text "✅ Office ottimizzato: telemetria, privacy e task pianificati rimossi." +} +function VcardAnalizer { + [CmdletBinding()] param( - [int]$Seconds = 30, - [string]$Message = "Riavvio automatico", - [switch]$Suppress + [string]$OverridesPath ) - if ($Suppress) { - return $true + $assetCacheDir = Join-Path $AppConfig.Paths.Root 'asset' + if (-not (Test-Path $assetCacheDir)) { + $null = New-Item -Path $assetCacheDir -ItemType Directory -Force + } + $defaultLocalOverrides = Join-Path $assetCacheDir 'DriverOverrides.json' + $resolvedOverridesPath = if ($OverridesPath) { $OverridesPath } else { $defaultLocalOverrides } + $analysis = [pscustomobject]@{ + Cards = @() + Matches = @() + PrimaryManufacturer = 'Unknown' + OverridesLoaded = $false + OverridesSource = $resolvedOverridesPath } - Write-StyledMessage -Type 'Info' -Text '💡 Premi un tasto qualsiasi per annullare...' - Write-Host '' - for ($i = $Seconds; $i -gt 0; $i--) { - if ([Console]::KeyAvailable) { - $null = [Console]::ReadKey($true) - Write-Host "`n" - Write-StyledMessage -Type 'Warning' -Text '⏸️ Riavvio del sistema annullato.' - return $false + try { + $cards = Get-CimInstance Win32_VideoController -ErrorAction SilentlyContinue + foreach ($card in $cards) { + $name = [string]$card.Name + $caption = [string]$card.Caption + $pnpId = [string]$card.PNPDeviceID + $manufacturer = 'Unknown' + if ($name -match 'NVIDIA|GeForce|Quadro|Tesla' -or $caption -match 'NVIDIA') { $manufacturer = 'NVIDIA' } + elseif ($name -match 'AMD|Radeon|ATI' -or $caption -match 'AMD|ATI') { $manufacturer = 'AMD' } + elseif ($name -match 'Intel|Iris|UHD|HD Graphics' -or $caption -match 'Intel') { $manufacturer = 'Intel' } + $analysis.Cards += [pscustomobject]@{ + Name = $name + Caption = $caption + PnpDeviceID = $pnpId + Manufacturer = $manufacturer + } } - $percent = [Math]::Round((($Seconds - $i) / $Seconds) * 100) - $filled = [Math]::Floor($percent * 20 / 100) - $remaining = 20 - $filled - $bar = "[$('█' * $filled)$('▒' * $remaining)]" - Write-Host "`r⏰ $Message tra $i secondi $bar" -NoNewline -ForegroundColor Red - Start-Sleep 1 } - Write-Host "`n" - return $true -} -function Get-SystemInfo { + catch { + Write-StyledMessage -Type 'Warning' -Text "Analisi GPU: errore durante lettura Win32_VideoController: $($_.Exception.Message)" + } + if ($analysis.Cards.Count -gt 0) { + $analysis.PrimaryManufacturer = ($analysis.Cards | Select-Object -First 1).Manufacturer + } + $overrides = @() + $remoteUrl = $AppConfig.URLs.DriverOverridesJson + if ([string]::IsNullOrWhiteSpace($remoteUrl)) { + $remoteUrl = "$($AppConfig.URLs.GitHubAssetBaseUrl)DriverOverrides.json" + } try { - $osInfo = Get-CimInstance Win32_OperatingSystem - $computerInfo = Get-CimInstance Win32_ComputerSystem - $diskInfo = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" - $versionMap = @{ - 28000 = "26H1"; 26200 = "25H2"; 26100 = "24H2"; 22631 = "23H2"; 22621 = "22H2"; 22000 = "21H2"; - 19045 = "22H2"; 19044 = "21H2"; 19043 = "21H1"; 19042 = "20H2"; 19041 = "2004"; 18363 = "1909"; - 18362 = "1903"; 17763 = "1809"; 17134 = "1803"; 16299 = "1709"; 15063 = "1703"; 14393 = "1607"; - 10586 = "1511"; 10240 = "1507" + if (Invoke-ToolkitDownload -Uri $remoteUrl -OutputPath $defaultLocalOverrides -Description 'Driver Overrides JSON') { + $resolvedOverridesPath = $defaultLocalOverrides + $analysis.OverridesSource = $resolvedOverridesPath } - $build = [int]$osInfo.BuildNumber - $ver = "N/A" - foreach ($k in ($versionMap.Keys | Sort -Desc)) { if ($build -ge $k) { $ver = $versionMap[$k]; break } } - return @{ - ProductName = $osInfo.Caption -replace 'Microsoft ', ''; BuildNumber = $build; DisplayVersion = $ver - Architecture = $osInfo.OSArchitecture; ComputerName = $computerInfo.Name - TotalRAM = [Math]::Round($computerInfo.TotalPhysicalMemory / 1GB, 2) - TotalDisk = [Math]::Round($diskInfo.Size / 1GB, 0) - FreeDisk = [Math]::Round($diskInfo.FreeSpace / 1GB, 0) - FreePercentage = [Math]::Round(($diskInfo.FreeSpace / $diskInfo.Size) * 100, 0) + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Download DriverOverrides.json fallito, uso cache locale se disponibile." + } + if (Test-Path $resolvedOverridesPath) { + try { + $jsonRaw = Get-Content -Path $resolvedOverridesPath -Raw -Encoding UTF8 + $parsed = $jsonRaw | ConvertFrom-Json -ErrorAction Stop + if ($parsed -is [System.Array]) { $overrides = $parsed } + elseif ($parsed) { $overrides = @($parsed) } + $analysis.OverridesLoaded = $true + } + catch { + Write-StyledMessage -Type 'Warning' -Text "DriverOverrides.json non valido: $($_.Exception.Message)" } } - catch { return $null } -} -function Get-BitlockerStatus { - try { - $out = & manage-bde -status C: 2>&1 - if ($out -match "Stato protezione:\s*(.*)") { return $matches[1].Trim() } - return "Non configurato" + else { + Write-StyledMessage -Type 'Warning' -Text "DriverOverrides.json non trovato in $resolvedOverridesPath" + } + foreach ($gpu in $analysis.Cards) { + foreach ($ovr in $overrides) { + $namePattern = [string]$ovr.NamePattern + $pnpPattern = [string]$ovr.PnpIdPattern + $manufacturer = [string]$ovr.Manufacturer + $nameMatches = $false + $pnpMatches = $false + $mfrMatches = $false + if (-not [string]::IsNullOrWhiteSpace($namePattern) -and -not [string]::IsNullOrWhiteSpace($gpu.Name)) { + $nameMatches = $gpu.Name -match $namePattern + } + if (-not [string]::IsNullOrWhiteSpace($pnpPattern) -and -not [string]::IsNullOrWhiteSpace($gpu.PnpDeviceID)) { + $pnpMatches = $gpu.PnpDeviceID -like $pnpPattern + } + if (-not [string]::IsNullOrWhiteSpace($manufacturer) -and $gpu.Manufacturer -ne 'Unknown') { + $mfrMatches = $gpu.Manufacturer -eq $manufacturer + } + if (($nameMatches -or $pnpMatches) -and ($mfrMatches -or [string]::IsNullOrWhiteSpace($manufacturer))) { + $analysis.Matches += [pscustomobject]@{ + Key = [string]$ovr.Key + Manufacturer = [string]$ovr.Manufacturer + NamePattern = [string]$ovr.NamePattern + PnpIdPattern = [string]$ovr.PnpIdPattern + DownloadUrl = [string]$ovr.DownloadUrl + FileName = [string]$ovr.FileName + DisplayName = [string]$ovr.DisplayName + MatchedGpu = [string]$gpu.Name + MatchedPnpId = [string]$gpu.PnpDeviceID + } + } + } } - catch { return "Disattivato" } -} -function WinOSCheck { - if ($Global:GuiSessionActive) { - return + if ($analysis.Matches.Count -gt 0) { + $analysis.Matches = @($analysis.Matches | Group-Object Key | ForEach-Object { $_.Group | Select-Object -First 1 }) + Write-StyledMessage -Type 'Success' -Text "Rilevate $($analysis.Matches.Count) corrispondenze driver stabili da DriverOverrides.json." } - Show-Header -SubTitle "System Check" - $si = Get-SystemInfo - if (-not $si) { Write-StyledMessage -Type 'Warning' -Text "Info sistema non disponibili."; return } - Write-StyledMessage -Type 'Info' -Text "Sistema: $($si.ProductName) ($($si.DisplayVersion))" - if ($si.BuildNumber -ge 22000) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win11/10 recente)." } - elseif ($si.BuildNumber -ge 17763) { Write-StyledMessage -Type 'Success' -Text "Sistema compatibile (Win10)." } - elseif ($si.BuildNumber -eq 9600) { Write-StyledMessage -Type 'Warning' -Text "Windows 8.1: Compatibilità parziale." } else { - Write-StyledMessage -Type 'Error' -Text "$(Center-Text '🤣 ERRORE CRITICO 🤣' 65)" - Write-StyledMessage -Type 'Error' -Text "Davvero pensi che questo script possa fare qualcosa per questa versione?" - Write-Host " Vuoi rischiare? [Y/N]" -ForegroundColor Yellow - if ((Read-Host) -notmatch '^[Yy]$') { exit } + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato per le GPU rilevate." } - Start-Sleep -Seconds 2 + $Global:VcardAnalysisResult = $analysis + return $analysis } function WinRepairToolkit { [CmdletBinding()] param( - [Parameter(Mandatory = $false)] [int]$MaxRetryAttempts = 3, - [Parameter(Mandatory = $false)] [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "WinRepairToolkit" - Show-Header -SubTitle "Repair Toolkit" - $Host.UI.RawUI.WindowTitle = "Repair Toolkit By MagnetarMan" + Start-ToolkitSession -ToolName "WinRepairToolkit" -SubTitle "Repair Toolkit" $script:CurrentAttempt = 0 $sysInfo = Get-SystemInfo - $isWin11_24H2_OrNewer = $sysInfo -and ($sysInfo.BuildNumber -ge 26100) $RepairTools = @( @{ Tool = 'chkdsk'; Args = @('/scan', '/perf'); Name = 'Controllo disco'; Icon = '💽' } @{ Tool = 'sfc'; Args = @('/scannow'); Name = 'Controllo file di sistema (1)'; Icon = '🗂️' } @@ -1048,68 +1540,67 @@ function WinRepairToolkit { @{ Tool = 'sfc'; Args = @('/scannow'); Name = 'Controllo file di sistema (2)'; Icon = '🗂️' } @{ Tool = 'chkdsk'; Args = @('/f', '/r', '/x'); Name = 'Controllo disco approfondito'; Icon = '💽'; IsCritical = $false } ) - if ($isWin11_24H2_OrNewer) { - $RepairTools += @{ Tool = 'powershell.exe'; Args = @('-Command', "if (Test-Path 'C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy\appxmanifest.xml') { Add-AppxPackage -Register -Path 'C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy\appxmanifest.xml' -DisableDevelopmentMode -ErrorAction SilentlyContinue } else { Write-Host 'File non trovato: MicrosoftWindows.Client.CBS_cw5n1h2txyewy' }"); Name = 'Registrazione AppX (Client CBS)'; Icon = '📦'; IsCritical = $false } - $RepairTools += @{ Tool = 'powershell.exe'; Args = @('-Command', "if (Test-Path 'C:\Windows\SystemApps\Microsoft.UI.Xaml.CBS_8wekyb3d8bbwe\appxmanifest.xml') { Add-AppxPackage -Register -Path 'C:\Windows\SystemApps\Microsoft.UI.Xaml.CBS_8wekyb3d8bbwe\appxmanifest.xml' -DisableDevelopmentMode -ErrorAction SilentlyContinue } else { Write-Host 'File non trovato: Microsoft.UI.Xaml.CBS_8wekyb3d8bbwe' }"); Name = 'Registrazione AppX (UI Xaml CBS)'; Icon = '📦'; IsCritical = $false } - $RepairTools += @{ Tool = 'powershell.exe'; Args = @('-Command', "if (Test-Path 'C:\Windows\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\appxmanifest.xml') { Add-AppxPackage -Register -Path 'C:\Windows\SystemApps\MicrosoftWindows.Client.Core_cw5n1h2txyewy\appxmanifest.xml' -DisableDevelopmentMode -ErrorAction SilentlyContinue } else { Write-Host 'File non trovato: MicrosoftWindows.Client.Core_cw5n1h2txyewy' }"); Name = 'Registrazione AppX (Client Core)'; Icon = '📦'; IsCritical = $false } - } function Invoke-RepairCommand { param([hashtable]$Config, [int]$Step, [int]$Total) - Write-StyledMessage Info "[$Step/$Total] Avvio $($Config.Name)." + Write-StyledMessage -Type 'Info' -Text "[$Step/$Total] Avvio $($Config.Name)." $isChkdsk = ($Config.Tool -ieq 'chkdsk') $outFile = [System.IO.Path]::GetTempFileName() $errFile = [System.IO.Path]::GetTempFileName() try { $processTimeoutSeconds = 600 switch ($Config.Name) { - 'Ripristino immagine Windows' { $processTimeoutSeconds = 3600 } + 'Ripristino immagine Windows' { $processTimeoutSeconds = 10800 } 'Controllo file di sistema (1)' { $processTimeoutSeconds = 3600 } - 'Controllo file di sistema (2)' { $processTimeoutSeconds = 3600 } + 'Controllo file di sistema (2)' { $processTimeoutSeconds = 10800 } 'Pulizia Residui Aggiornamenti' { $processTimeoutSeconds = 3600 } 'Controllo disco' { $processTimeoutSeconds = 900 } - 'Controllo disco approfondito' { $processTimeoutSeconds = 900 } + 'Controllo disco approfondito' { $processTimeoutSeconds = 3600 } } $spinnerUpdateInterval = if ($Config.Name -eq 'Ripristino immagine Windows') { 900 } else { 600 } - $result = Invoke-WithSpinner -Activity $Config.Name -Process -Action { - if ($isChkdsk -and ($Config.Args -contains '/f' -or $Config.Args -contains '/r')) { - $drive = ($Config.Args | Where-Object { $_ -match '^[A-Za-z]:$' } | Select-Object -First 1) ?? $env:SystemDrive - $filteredArgs = $Config.Args | Where-Object { $_ -notmatch '^[A-Za-z]:$' } - $procParams = @{ - FilePath = 'cmd.exe' - ArgumentList = @('/c', "echo Y| chkdsk $drive $($filteredArgs -join ' ')") - RedirectStandardOutput = $outFile - RedirectStandardError = $errFile - NoNewWindow = $true - PassThru = $true - } - Start-Process @procParams - } - else { - $procParams = @{ - FilePath = $Config.Tool - ArgumentList = $Config.Args - RedirectStandardOutput = $outFile - RedirectStandardError = $errFile - NoNewWindow = $true - PassThru = $true - } - Start-Process @procParams - } - } -TimeoutSeconds $processTimeoutSeconds -UpdateInterval $spinnerUpdateInterval - $results = @() - @($outFile, $errFile) | Where-Object { Test-Path $_ } | ForEach-Object { - $results += Get-Content $_ -ErrorAction SilentlyContinue - } + if ($Config.Tool -ieq 'DISM' -and $Config.Args -contains '/StartComponentCleanup') { + Write-StyledMessage -Type 'Info' -Text "🔧 Pulizia stato Windows Update prima di avviare Cleanup..." + Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue + Start-Sleep 1 + Remove-ItemSafely -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\SessionsPending' -Recurse + Start-Sleep 1 + } + $commandToRun = $Config.Tool + $argsToRun = $Config.Args + if ($isChkdsk -and ($Config.Args -contains '/f' -or $Config.Args -contains '/r')) { + $drive = ($Config.Args | Where-Object { $_ -match '^[A-Za-z]:$' } | Select-Object -First 1) ?? $env:SystemDrive + $filteredArgs = $Config.Args | Where-Object { $_ -notmatch '^[A-Za-z]:$' } + $commandToRun = 'cmd.exe' + $argsToRun = @('/c', "echo Y| chkdsk $drive $($filteredArgs -join ' ')") + } + $spinnerResult = Invoke-WithSpinner -Activity $Config.Name ` + -Command $commandToRun ` + -Arguments $argsToRun ` + -TimeoutSeconds $processTimeoutSeconds ` + -UpdateInterval $spinnerUpdateInterval ` + -LogContextKey "Repair-$($Config.Tool)" + $exitCode = $spinnerResult.ExitCode + $results = ($spinnerResult.StdOut + "`n" + $spinnerResult.StdErr) -split "`n" if ($isChkdsk -and ($Config.Args -contains '/f' -or $Config.Args -contains '/r') -and ($results -join ' ').ToLower() -match 'schedule|next time.*restart|volume.*in use') { - Write-StyledMessage Info "🔧 $($Config.Name): controllo schedulato al prossimo riavvio." + Write-StyledMessage -Type 'Info' -Text "🔧 $($Config.Name): controllo schedulato al prossimo riavvio." + return @{ Success = $true; ErrorCount = 0 } + } + $isTimeout = ($spinnerResult.TimedOut -eq $true) -or ($null -eq $exitCode) -or ($exitCode -eq -1) + if ($isChkdsk -and $exitCode -eq 3) { + Write-StyledMessage -Type 'Info' -Text "🔧 $($Config.Name): controllo schedulato al prossimo riavvio." + return @{ Success = $true; ErrorCount = 0 } + } + if (($Config.Tool -ieq 'DISM') -and ($results -match '0x800f0806')) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ $($Config.Name): Errore 0x800f0806 (operazioni pendenti). Questo non è un errore critico." + Write-StyledMessage -Type 'Info' -Text "💡 Riavviare il sistema per completare le operazioni in sospeso." return @{ Success = $true; ErrorCount = 0 } } - $exitCode = $result.ExitCode - $isTimeout = ($null -eq $result) -or ($null -eq $exitCode) -or ($exitCode -eq -1) $hasDismSuccess = (-not $isTimeout) -and ($Config.Tool -ieq 'DISM') -and ($results -match '(?i)completed successfully') + if (($Config.Tool -ieq 'DISM') -and ($Config.Args -contains '/ResetBase') -and $exitCode -eq 3010) { + $hasDismSuccess = $true + } $isChkdskScan = $isChkdsk -and ($Config.Args -contains '/scan') $chkdskCompleted = (-not $isTimeout) -and $isChkdskScan -and (($results -join ' ') -match '(?i)(scansione.*completata|scan.*completed|successfully scanned)') - $isSuccess = (-not $isTimeout) -and (($exitCode -eq 0) -or $hasDismSuccess -or $chkdskCompleted) + $isSuccess = (-not $isTimeout) -and (($exitCode -eq 0) -or $exitCode -eq 3010 -or $hasDismSuccess -or $chkdskCompleted) $errors = $warnings = @() if (-not $isSuccess) { if ($isTimeout) { @@ -1125,7 +1616,9 @@ function WinRepairToolkit { } } else { - if ($trim -match '(?i)(errore|error|failed|impossibile|corrotto|corruption)') { $errors += $trim } + if ($trim -match '0x800f0806') { + } + elseif ($trim -match '(?i)(errore|error|failed|impossibile|corrotto|corruption)') { $errors += $trim } elseif ($trim -match '(?i)(warning|avviso|attenzione)') { $warnings += $trim } } } @@ -1140,7 +1633,7 @@ function WinRepairToolkit { else { $message = "$($Config.Name) completato " + $(if ($success) { 'con successo' } else { "con $($errors.Count) errori" }) } - Write-StyledMessage $(if ($success) { 'Success' } else { 'Warning' }) $message + Write-StyledMessage -Type 'Success' -Text $message if ($Config.Tool -ieq 'sfc') { $cbsLogPath = "C:\Windows\Logs\CBS\CBS.log" if (Test-Path $cbsLogPath) { @@ -1151,43 +1644,25 @@ function WinRepairToolkit { $destLogPath = Join-Path $AppConfig.Paths.Logs $destLogName Copy-Item -Path $cbsLogPath -Destination $destLogPath -Force -ErrorAction SilentlyContinue if (Test-Path $destLogPath) { - Write-StyledMessage Info "📄 Log SFC salvato in: $destLogName" + Write-StyledMessage -Type 'Info' -Text "📄 Log SFC salvato in: $destLogName" } } catch { - Write-StyledMessage Warning "⚠️ Impossibile esportare il log CBS di SFC (file in uso)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ Impossibile esportare il log CBS di SFC (file in uso)." } } } return @{ Success = $success; ErrorCount = $errors.Count } } catch { - Write-StyledMessage Error "Errore durante $($Config.Name): $($_.Exception.Message)." - Write-ToolkitLog -Level ERROR -Message "Errore in Invoke-RepairCommand [$($Config.Tool)]" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace - } + Write-ToolkitError -Record $_ -ToolName "WinRepairToolkit" -Message "Errore in Invoke-RepairCommand [$($Config.Tool)]" return @{ Success = $false; ErrorCount = 1 } } - finally { - foreach ($f in @($outFile, $errFile)) { - if (Test-Path $f) { - $raw = Get-Content $f -Raw -Encoding UTF8 -ErrorAction SilentlyContinue - if (-not [string]::IsNullOrWhiteSpace($raw)) { - $label = if ($f -eq $outFile) { 'STDOUT' } else { 'STDERR' } - Write-ToolkitLog -Level DEBUG -Message "[PROCESS $label`: $($Config.Tool)]`n$raw" - } - Remove-Item $f -ErrorAction SilentlyContinue - } - } - } } function Start-RepairCycle { param([int]$Attempt = 1) $script:CurrentAttempt = $Attempt - Write-StyledMessage Info "🔄 Tentativo $Attempt/$MaxRetryAttempts - Riparazione sistema." - Write-Host '' + Write-StyledMessage -Type 'Info' -Text "🔄 Tentativo $Attempt/$MaxRetryAttempts - Riparazione sistema." $totalErrors = $successCount = 0 for ($toolIndex = 0; $toolIndex -lt $RepairTools.Count; $toolIndex++) { $result = Invoke-RepairCommand -Config $RepairTools[$toolIndex] -Step ($toolIndex + 1) -Total $RepairTools.Count @@ -1198,7 +1673,7 @@ function WinRepairToolkit { Start-Sleep 1 } if ($totalErrors -gt 0 -and $Attempt -lt $MaxRetryAttempts) { - Write-StyledMessage Warning "🔄 $totalErrors errori rilevati. Nuovo tentativo." + Write-StyledMessage -Type 'Warning' -Text "🔄 $totalErrors errori rilevati. Nuovo tentativo." Start-Sleep 3 return Start-RepairCycle -Attempt ($Attempt + 1) } @@ -1221,35 +1696,52 @@ function WinRepairToolkit { return $true } catch { - Write-ToolkitLog -Level 'ERROR' -Message 'Eccezione in Start-DeepDiskRepair' -Context @{ - Tool = 'WinRepairToolkit' - Step = 'Start-DeepDiskRepair' - Exception = $_.Exception.Message - Stack = $_.ScriptStackTrace - } - Write-StyledMessage -Type 'Error' -Text "Errore durante la schedulazione della riparazione profonda: $($_.Exception.Message)." + Write-ToolkitError -Record $_ -ToolName "WinRepairToolkit" -Message "Eccezione in Start-DeepDiskRepair" return $false } } + function Test-PendingOperations { + $pendingRebootKeys = @( + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending', + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', + 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' + ) + foreach ($key in $pendingRebootKeys) { + if (Test-Path $key) { + $values = Get-ItemProperty $key -ErrorAction SilentlyContinue + if ($values -and $values.PSObject.Properties.Count -gt 1) { + return $true + } + } + } + return $false + } + if (Test-PendingOperations) { + Write-ToolkitLog -Level WARNING -Message "Rilevate operazioni pendenti che richiedono riavvio. DISM potrebbe fallire." -Context @{ + Tool = 'WinRepairToolkit' + Step = 'PreExecutionCheck' + } + Write-StyledMessage -Type 'Warning' -Text "⚠️ Rilevate operazioni pendenti che richiedono riavvio. DISM potrebbe fallire." + Write-StyledMessage -Type 'Info' -Text "💡 Consigliato riavviare prima di eseguire le riparazioni." + } try { $repairResult = Start-RepairCycle $deepRepairScheduled = $false if ($repairResult.TotalErrors -gt 0) { - Write-StyledMessage Warning "Rilevati errori persistenti. Avvio riparazione profonda." + Write-ToolkitLog -Level WARNING -Message "Rilevati errori persistenti. Avvio riparazione profonda." -Context @{ + Tool = 'WinRepairToolkit' + Step = 'RepairCycle' + TotalErrors = $repairResult.TotalErrors + } + Write-StyledMessage -Type 'Warning' -Text "Rilevati errori persistenti. Avvio riparazione profonda." $deepRepairScheduled = Start-DeepDiskRepair } else { - Write-StyledMessage Success "Sistema in salute. Riparazione profonda non necessaria." + Write-StyledMessage -Type 'Success' -Text "Sistema in salute. Riparazione profonda non necessaria." } - Write-StyledMessage Info "⚙️ Impostazione scadenza password illimitata." - $procParams = @{ - FilePath = 'net' - ArgumentList = @('accounts', '/maxpwage:unlimited') - NoNewWindow = $true - Wait = $true - } - Start-Process @procParams - if ($deepRepairScheduled) { Write-StyledMessage Warning 'Riavvio necessario per riparazione profonda.' } + Write-StyledMessage -Type 'Info' -Text "⚙️ Impostazione scadenza password illimitata." + $null = Invoke-ExternalCommandWithLog -Command 'net' -Arguments @('accounts', '/maxpwage:unlimited') -TimeoutSeconds 30 -LogContextKey 'Repair-NetAccounts' + if ($deepRepairScheduled) { Write-StyledMessage -Type 'Warning' -Text 'Riavvio necessario per riparazione profonda.' } if ($SuppressIndividualReboot) { if ($deepRepairScheduled) { $Global:NeedsFinalReboot = $true @@ -1263,25 +1755,18 @@ function WinRepairToolkit { } } catch { - Write-StyledMessage Error "❌ Errore critico: $($_.Exception.Message)." - Write-ToolkitLog -Level ERROR -Message "Errore critico in WinRepairToolkit" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace - } + Write-ToolkitError -Record $_ -ToolName "WinRepairToolkit" + } + finally { } } function WinUpdateReset { [CmdletBinding()] param( - [Parameter(Mandatory = $false)] [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "WinUpdateReset" - Show-Header -SubTitle "Update Reset Toolkit" - $Host.UI.RawUI.WindowTitle = "Win Update Reset Toolkit By MagnetarMan" + Start-ToolkitSession -ToolName "WinUpdateReset" -SubTitle "Update Reset Toolkit" function Set-ServiceStatus { param ( [Parameter(Mandatory = $true)][string]$Name, @@ -1308,8 +1793,7 @@ function WinUpdateReset { return $true } function Show-ServiceProgress([string]$ServiceName, [string]$Action, [int]$Current, [int]$Total) { - $percent = [math]::Round(($Current / $Total) * 100) - Invoke-WithSpinner -Activity "$Action $ServiceName" -Timer -Action { Start-Sleep -Milliseconds 200 } -TimeoutSeconds 1 | Out-Null + Invoke-WithSpinner -Activity "$Action $ServiceName" -Timer -Action { Start-Sleep -Milliseconds 200 } -TimeoutSeconds 1 *>$null } function Manage-Service($serviceName, $action, $config, $currentStep, $totalSteps) { try { @@ -1332,17 +1816,14 @@ function WinUpdateReset { } 'Configure' { Show-ServiceProgress $serviceName "Configurazione" $currentStep $totalSteps - Set-Service -Name $serviceName -StartupType $config.Type -ErrorAction Stop | Out-Null + Set-Service -Name $serviceName -StartupType $config.Type -ErrorAction Stop *>$null Write-StyledMessage -Type 'Success' -Text "$serviceIcon Servizio $serviceName configurato come $($config.Type)." } 'Start' { Show-ServiceProgress $serviceName "Avvio" $currentStep $totalSteps - $success = $false - Invoke-WithSpinner -Activity "Attesa avvio $serviceName" -Timer -Action { - $success = Set-ServiceStatus -Name $serviceName -Status 'Running' -Wait -TimeoutSeconds 10 - } -TimeoutSeconds 5 | Out-Null - $clearLine = "`r" + (' ' * 80) + "`r" - Write-Host $clearLine -NoNewline + Invoke-WithSpinner -Activity "Attesa avvio $serviceName" -Timer -Action { Start-Sleep -Milliseconds 200 } -TimeoutSeconds 1 *>$null + $success = Set-ServiceStatus -Name $serviceName -Status 'Running' -Wait -TimeoutSeconds 10 + Clear-ProgressLine if ($success) { Write-StyledMessage -Type 'Success' -Text "$serviceIcon Servizio ${serviceName}: avviato correttamente." } @@ -1365,42 +1846,28 @@ function WinUpdateReset { } function Remove-DirectorySafely([string]$path, [string]$displayName) { if (-not (Test-Path $path)) { - $clearLines = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLines -NoNewline + Clear-ProgressLine [Console]::Out.Flush() Write-StyledMessage -Type 'Info' -Text "💭 Directory $displayName non presente." return $true } try { - $ErrorActionPreference = 'SilentlyContinue' - $ProgressPreference = 'SilentlyContinue' - $VerbosePreference = 'SilentlyContinue' Remove-Item $path -Recurse -Force -ErrorAction SilentlyContinue *>$null - $clearLines = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLines -NoNewline + Clear-ProgressLine [Console]::Out.Flush() Write-StyledMessage -Type 'Success' -Text "🗑️ Directory $displayName eliminata." return $true } catch { - $clearLines = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLines -NoNewline + Clear-ProgressLine Write-StyledMessage -Type 'Warning' -Text "Tentativo fallito, provo con eliminazione forzata." try { $tempDir = [System.IO.Path]::GetTempPath() + "empty_" + [System.Guid]::NewGuid().ToString("N").Substring(0, 8) $null = New-Item -ItemType Directory -Path $tempDir -Force - $procParams = @{ - FilePath = 'robocopy.exe' - ArgumentList = @("`"$tempDir`"", "`"$path`"", '/MIR', '/NFL', '/NDL', '/NJH', '/NJS', '/NP', '/NC') - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - $null = Start-Process @procParams - Remove-Item $tempDir -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item $path -Force -ErrorAction SilentlyContinue | Out-Null - $clearLines = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLines -NoNewline + $null = Invoke-WithSpinner -Activity "Pulizia $displayName" -Command 'robocopy.exe' -Arguments @("`"$tempDir`"", "`"$path`"", '/MIR', '/NFL', '/NDL', '/NJH', '/NJS', '/NP', '/NC') -TimeoutSeconds 300 -LogContextKey 'RemoveDirectorySafely-Robocopy' + Remove-Item $tempDir -Force -ErrorAction SilentlyContinue *>$null + Remove-Item $path -Force -ErrorAction SilentlyContinue *>$null + Clear-ProgressLine [Console]::Out.Flush() if (-not (Test-Path $path)) { Write-StyledMessage -Type 'Success' -Text "🗑️ Directory $displayName eliminata (metodo forzato)." @@ -1415,15 +1882,10 @@ function WinUpdateReset { Write-StyledMessage -Type 'Warning' -Text "Impossibile eliminare completamente $displayName - file in uso." return $false } - finally { - $ErrorActionPreference = 'Continue' - $ProgressPreference = 'Continue' - $VerbosePreference = 'SilentlyContinue' - } } } Write-StyledMessage -Type 'Info' -Text '🔧 Inizializzazione dello Script di Reset Windows Update.' - Invoke-WithSpinner -Activity "Caricamento moduli" -Timer -Action { Start-Sleep 2 } -TimeoutSeconds 2 | Out-Null + Invoke-WithSpinner -Activity "Caricamento moduli" -Timer -Action { Start-Sleep 2 } -TimeoutSeconds 2 *>$null Write-StyledMessage -Type 'Info' -Text '🛠️ Avvio riparazione servizi Windows Update.' $serviceConfig = @{ 'wuauserv' = @{ Type = 'Automatic'; Critical = $true; Icon = '🔄'; DisplayName = 'Windows Update' } @@ -1449,6 +1911,29 @@ function WinUpdateReset { for ($serviceIndex = 0; $serviceIndex -lt $stopServices.Count; $serviceIndex++) { Manage-Service $stopServices[$serviceIndex] 'Stop' $serviceConfig[$stopServices[$serviceIndex]] ($serviceIndex + 1) $stopServices.Count } + Write-StyledMessage -Type 'Info' -Text '🧹 Pulizia cache GPCache e impostazioni WSUS.' + try { + if (Test-Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UpdatePolicy\GPCache") { + Remove-Item "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UpdatePolicy\GPCache" -Recurse -Force -ErrorAction Stop + Write-StyledMessage -Type 'Success' -Text "🗑️ Cache GPCache eliminata." + } else { + Write-StyledMessage -Type 'Info' -Text "💭 Cache GPCache non presente." + } + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Avviso: Impossibile eliminare cache GPCache - $($_.Exception.Message)." + } + try { + if (Test-Path "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate") { + Remove-Item "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate" -Recurse -Force -ErrorAction Stop + Write-StyledMessage -Type 'Success' -Text "🔑 Impostazioni WSUS rimosse." + } else { + Write-StyledMessage -Type 'Info' -Text "💭 Impostazioni WSUS non presenti." + } + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Avviso: Impossibile rimuovere impostazioni WSUS - $($_.Exception.Message)." + } Write-StyledMessage -Type 'Info' -Text '⏳ Attesa liberazione risorse.' Start-Sleep -Seconds 3 Write-StyledMessage -Type 'Info' -Text '⚙️ Ripristino configurazione servizi Windows Update.' @@ -1464,13 +1949,13 @@ function WinUpdateReset { Manage-Service $sysService.Name 'Check' @{ Icon = $sysService.Icon } ($systemIndex + 1) $systemServices.Count } Write-StyledMessage -Type 'Info' -Text '📋 Ripristino chiavi di registro Windows Update.' - Invoke-WithSpinner -Activity "Elaborazione registro" -Timer -Action { Start-Sleep 1 } -TimeoutSeconds 1 | Out-Null + Invoke-WithSpinner -Activity "Elaborazione registro" -Timer -Action { Start-Sleep 1 } -TimeoutSeconds 1 *>$null try { @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update", "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" ) | Where-Object { Test-Path $_ } | ForEach-Object { - Remove-Item $_ -Recurse -Force -ErrorAction Stop | Out-Null + Remove-Item $_ -Recurse -Force -ErrorAction Stop *>$null Write-StyledMessage -Type 'Success' -Text 'Completato!' } if (-not @("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update", "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate") | Where-Object { Test-Path $_ }) { @@ -1484,27 +1969,26 @@ function WinUpdateReset { } Write-StyledMessage -Type 'Info' -Text '🗂️ Eliminazione componenti Windows Update.' $directories = @( - @{ Path = "$env:WinDir\SoftwareDistribution"; Name = "SoftwareDistribution" }, - @{ Path = "$env:WinDir\System32\catroot2"; Name = "catroot2" }, - @{ Path = "$env:WinDir\System32\WaaSMedicSvc.dll"; Name = "WaaSMedicSvc.dll" }, - @{ Path = "$env:WinDir\System32\wuaueng.dll"; Name = "wuaueng.dll" }, - @{ Path = "$env:WinDir\System32\WaaSMedicSvc_BAK.dll"; Name = "WaaSMedicSvc_BAK.dll" }, - @{ Path = "$env:WinDir\System32\wuaueng_BAK.dll"; Name = "wuaueng_BAK.dll" }, - @{ Path = "$env:WinDir\SoftwareDistribution\Download"; Name = "Download" }, - @{ Path = "$env:WinDir\SoftwareDistribution\DataStore"; Name = "DataStore" }, - @{ Path = "$env:WinDir\SoftwareDistribution\Backup"; Name = "Backup" } + @{ Path = $AppConfig.Paths.SoftwareDistribution; Name = "SoftwareDistribution" }, + @{ Path = $AppConfig.Paths.Catroot2; Name = "catroot2" }, + @{ Path = Join-Path $AppConfig.Paths.System32 "WaaSMedicSvc.dll"; Name = "WaaSMedicSvc.dll" }, + @{ Path = Join-Path $AppConfig.Paths.System32 "wuaueng.dll"; Name = "wuaueng.dll" }, + @{ Path = Join-Path $AppConfig.Paths.System32 "WaaSMedicSvc_BAK.dll"; Name = "WaaSMedicSvc_BAK.dll" }, + @{ Path = Join-Path $AppConfig.Paths.System32 "wuaueng_BAK.dll"; Name = "wuaueng_BAK.dll" }, + @{ Path = Join-Path $AppConfig.Paths.SoftwareDistribution "Download"; Name = "Download" }, + @{ Path = Join-Path $AppConfig.Paths.SoftwareDistribution "DataStore"; Name = "DataStore" }, + @{ Path = Join-Path $AppConfig.Paths.SoftwareDistribution "Backup"; Name = "Backup" } ) for ($dirIndex = 0; $dirIndex -lt $directories.Count; $dirIndex++) { $dir = $directories[$dirIndex] $percent = [math]::Round((($dirIndex + 1) / $directories.Count) * 100) - Show-ProgressBar "Directory ($($dirIndex + 1)/$($directories.Count))" "Eliminazione $($dir.Name)" $percent '🗑️' '' 'Yellow' + Write-ProgressUpdate -Activity "Directory ($($dirIndex + 1)/$($directories.Count))" -Status "Eliminazione $($dir.Name)" -Percent $percent -Icon '🗑️' -Color 'Yellow' Start-Sleep -Milliseconds 300 $success = Remove-DirectorySafely -path $dir.Path -displayName $dir.Name if (-not $success) { Write-StyledMessage -Type 'Info' -Text "💡 Suggerimento: Alcuni file potrebbero essere ricreati dopo il riavvio." } - $clearLine = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLine -NoNewline + Clear-ProgressLine [Console]::Out.Flush() Start-Sleep -Milliseconds 500 } @@ -1514,35 +1998,20 @@ function WinUpdateReset { for ($essentialIndex = 0; $essentialIndex -lt $essentialServices.Count; $essentialIndex++) { Manage-Service $essentialServices[$essentialIndex] 'Start' $serviceConfig[$essentialServices[$essentialIndex]] ($essentialIndex + 1) $essentialServices.Count } - Write-StyledMessage -Type 'Progress' -Text '⚡ Esecuzione comando reset. ' - try { - $procParams = @{ - FilePath = 'cmd.exe' - ArgumentList = '/c', 'wuauclt', '/resetauthorization', '/detectnow' - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams | Out-Null - Write-StyledMessage -Type 'Success' -Text 'Completato!' - Write-StyledMessage -Type 'Success' -Text "🔄 Client Windows Update reimpostato." + Write-StyledMessage -Type 'Info' -Text '⚡ Esecuzione reset client Windows Update...' + $result = Invoke-WithSpinner -Activity 'Reset Client Update' -Command 'cmd.exe' -Arguments @('/c', 'wuauclt', '/resetauthorization', '/detectnow') -TimeoutSeconds 60 -LogContextKey 'UpdateReset-Wuauclt' + if ($result.Success) { + Write-StyledMessage -Type 'Success' -Text "🔄 Client Windows Update reimpostato correttamente." } - catch { - Write-StyledMessage -Type 'Error' -Text 'Errore!' - Write-StyledMessage -Type 'Warning' -Text "Errore durante il reset del client Windows Update." + else { + Write-StyledMessage -Type 'Warning' -Text "⚠️ Reset client Windows Update non completato (possibile timeout)." } Write-StyledMessage -Type 'Info' -Text '🔧 Abilitazione Windows Update e servizi correlati.' Write-StyledMessage -Type 'Info' -Text '📋 Ripristino impostazioni registro Windows Update.' try { - If (!(Test-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU")) { - New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Force | Out-Null - } - Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -Type DWord -Value 0 - Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions" -Type DWord -Value 3 - If (!(Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config")) { - New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Force | Out-Null - } - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -Type DWord -Value 1 + Set-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -Value 0 + Set-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions" -Value 3 + Set-RegistryValue -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -Value 1 Write-StyledMessage -Type 'Success' -Text "🔑 Impostazioni registro Windows Update ripristinate." } catch { @@ -1570,17 +2039,10 @@ function WinUpdateReset { Write-StyledMessage -Type 'Info' -Text "$($service.Icon) Ripristino $($service.Name) a $($service.StartupType)." $serviceObj = Get-Service -Name $service.Name -ErrorAction SilentlyContinue if ($serviceObj) { - Set-Service -Name $service.Name -StartupType $service.StartupType -ErrorAction SilentlyContinue | Out-Null - $procParams = @{ - FilePath = 'sc.exe' - ArgumentList = 'failure', "$($service.Name)", 'reset= 86400 actions= restart/60000/restart/60000/restart/60000' - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams | Out-Null + Set-Service -Name $service.Name -StartupType $service.StartupType -ErrorAction SilentlyContinue *>$null + $null = Invoke-ExternalCommandWithLog -Command 'sc.exe' -Arguments @('failure', "$($service.Name)", 'reset= 86400', 'actions= restart/60000/restart/60000/restart/60000') -TimeoutSeconds 30 -LogContextKey "ServiceFailureReset-$($service.Name)" if ($service.StartupType -eq "Automatic") { - Set-ServiceStatus -Name $service.Name -Status "Running" -Wait -TimeoutSeconds 5 | Out-Null + Set-ServiceStatus -Name $service.Name -Status "Running" -Wait -TimeoutSeconds 5 *>$null } Write-StyledMessage -Type 'Success' -Text "$($service.Icon) Servizio $($service.Name) ripristinato." } @@ -1592,44 +2054,16 @@ function WinUpdateReset { Write-StyledMessage -Type 'Info' -Text '🔍 Ripristino DLL rinominate.' $dlls = @("WaaSMedicSvc", "wuaueng") foreach ($dll in $dlls) { - $dllPath = "$env:WinDir\System32\$dll.dll" - $backupPath = "$env:WinDir\System32\${dll}_BAK.dll" + $dllPath = Join-Path $AppConfig.Paths.System32 "$dll.dll" + $backupPath = Join-Path $AppConfig.Paths.System32 "${dll}_BAK.dll" if ((Test-Path $backupPath) -and !(Test-Path $dllPath)) { try { - $procParams = @{ - FilePath = 'takeown.exe' - ArgumentList = '/f', "`"$backupPath`"" - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams | Out-Null - $procParams = @{ - FilePath = 'icacls.exe' - ArgumentList = "`"$backupPath`"", '/grant', '*S-1-1-0:F' - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams | Out-Null - Rename-Item -Path $backupPath -NewName "$dll.dll" -ErrorAction SilentlyContinue | Out-Null + $null = Invoke-ExternalCommandWithLog -Command 'takeown.exe' -Arguments @('/f', "`"$backupPath`"") -TimeoutSeconds 30 -LogContextKey "DLLRestore-Takeown-$dll" + $null = Invoke-ExternalCommandWithLog -Command 'icacls.exe' -Arguments @("`"$backupPath`"", '/grant', '*S-1-1-0:F') -TimeoutSeconds 30 -LogContextKey "DLLRestore-IcaclsGrant-$dll" + Rename-Item -Path $backupPath -NewName "$dll.dll" -ErrorAction SilentlyContinue *>$null Write-StyledMessage -Type 'Success' -Text "Ripristinato ${dll}_BAK.dll a $dll.dll." - $procParams = @{ - FilePath = 'icacls.exe' - ArgumentList = "`"$dllPath`"", '/setowner', '"NT SERVICE\TrustedInstaller"' - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams | Out-Null - $procParams = @{ - FilePath = 'icacls.exe' - ArgumentList = "`"$dllPath`"", '/remove', '*S-1-1-0' - Wait = $true - WindowStyle = 'Hidden' - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams | Out-Null + $null = Invoke-ExternalCommandWithLog -Command 'icacls.exe' -Arguments @("`"$dllPath`"", '/setowner', '"NT SERVICE\TrustedInstaller"') -TimeoutSeconds 30 -LogContextKey "DLLRestore-IcaclsOwner-$dll" + $null = Invoke-ExternalCommandWithLog -Command 'icacls.exe' -Arguments @("`"$dllPath`"", '/remove', '*S-1-1-0') -TimeoutSeconds 30 -LogContextKey "DLLRestore-IcaclsRemove-$dll" } catch { Write-StyledMessage -Type 'Warning' -Text "Avviso: Impossibile ripristinare $dll.dll - $($_.Exception.Message)." @@ -1655,7 +2089,7 @@ function WinUpdateReset { try { $tasks = Get-ScheduledTask -TaskPath $taskPath -ErrorAction SilentlyContinue foreach ($task in $tasks) { - Enable-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -ErrorAction SilentlyContinue | Out-Null + Enable-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -ErrorAction SilentlyContinue *>$null Write-StyledMessage -Type 'Success' -Text "Task abilitato: $($task.TaskName)." } } @@ -1697,39 +2131,27 @@ function WinUpdateReset { Write-StyledMessage -Type 'Info' -Text '📋 Ripristino criteri locali Windows.' try { Write-StyledMessage -Type 'Info' -Text '⏳ Eliminazione criteri locali.' - $rdProc = Start-Process -FilePath "cmd.exe" -ArgumentList "/c RD /S /Q `"$env:WinDir\System32\GroupPolicy`"" -WindowStyle Hidden -ErrorAction SilentlyContinue -PassThru - $rdTimeout = 10 - while (-not $rdProc.HasExited -and $rdTimeout -gt 0) { - Start-Sleep -Seconds 1 - $rdTimeout-- - } - if (-not $rdProc.HasExited) { $rdProc | Stop-Process -Force -ErrorAction SilentlyContinue } + $null = Invoke-ExternalCommandWithLog -Command 'cmd.exe' -Arguments @('/c', 'RD', '/S', '/Q', "`"$(Join-Path $AppConfig.Paths.System32 "GroupPolicy")`"") -TimeoutSeconds 30 -LogContextKey 'GPReset-RD' Write-StyledMessage -Type 'Success' -Text '✅ Criteri eliminati.' Write-StyledMessage -Type 'Info' -Text '⏳ Aggiornamento criteri.' - $gpProc = Start-Process -FilePath "gpupdate.exe" -ArgumentList "/force" -WindowStyle Hidden -ErrorAction SilentlyContinue -PassThru - $gpTimeout = 15 - while (-not $gpProc.HasExited -and $gpTimeout -gt 0) { - Start-Sleep -Seconds 1 - $gpTimeout-- - } - if (-not $gpProc.HasExited) { - $gpProc | Stop-Process -Force -ErrorAction SilentlyContinue - Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate terminato per timeout." + $gpResult = Invoke-ExternalCommandWithLog -Command 'gpupdate.exe' -Arguments @('/force') -TimeoutSeconds 60 -LogContextKey 'GPReset-GPUpdate' + if (-not $gpResult.Success) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate terminato con errori o timeout." } else { Write-StyledMessage -Type 'Success' -Text '✅ Criteri aggiornati.' } - Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKCU:\Software\Microsoft\WindowsSelfHost" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKCU:\Software\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\Microsoft\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\WindowsStore\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\Microsoft\WindowsSelfHost" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\WOW6432Node\Microsoft\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\WindowsStore\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKCU:\Software\Microsoft\WindowsSelfHost" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKCU:\Software\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\Microsoft\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\WindowsStore\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\Microsoft\WindowsSelfHost" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\WOW6432Node\Microsoft\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Policies" -Recurse -Force -ErrorAction SilentlyContinue *>$null + Remove-Item -Path "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\WindowsStore\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue *>$null Write-StyledMessage -Type 'Success' -Text "📋 Criteri locali Windows ripristinati." } catch { @@ -1752,34 +2174,21 @@ function WinUpdateReset { } Write-StyledMessage -Type 'Info' -Text '💡 Windows Update dovrebbe ora funzionare normalmente.' Write-StyledMessage -Type 'Info' -Text '🔧 Verifica aprendo Impostazioni > Aggiornamento e sicurezza.' - Write-StyledMessage -Type 'Info' -Text '🔄 Se necessario, riavvia il sistema per applicare tutte le modifiche.' Write-StyledMessage -Type 'Info' -Text ('─' * 60) Write-StyledMessage -Type 'Success' -Text '🎉 Riparazione completata con successo!' Write-StyledMessage -Type 'Success' -Text '💻 Il sistema necessita di un riavvio per applicare tutte le modifiche.' Write-StyledMessage -Type 'Warning' -Text "⚡ Attenzione: il sistema verrà riavviato automaticamente." Write-StyledMessage -Type 'Info' -Text ('─' * 60) - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio individuale soppresso. Verrà gestito un riavvio finale." - } - else { - $shouldReboot = Start-InterruptibleCountdown $CountdownSeconds "Preparazione riavvio sistema" - if ($shouldReboot) { - Write-StyledMessage -Type 'Info' -Text "🔄 Riavvio in corso." - Restart-Computer -Force - } - } + Invoke-ToolkitReboot -Message "Preparazione riavvio sistema" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot } catch { Write-StyledMessage -Type 'Error' -Text '═════════════════════════════════════════════════════════════════' - Write-StyledMessage -Type 'Error' -Text "💥 Errore critico: $($_.Exception.Message)." + Write-StyledMessage -Type 'Error' -Text "💥 Errore critico: $($_.Exception.Message). Consulta il log in %LOCALAPPDATA%\WinToolkit\logs o in $Global:CurrentLogFile" Write-StyledMessage -Type 'Info' -Text '⌨️ Premere un tasto per uscire.' $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - Write-ToolkitLog -Level ERROR -Message "Errore critico in WinUpdateReset: $($_.Exception.Message)" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace - } + Write-ToolkitError -Record $_ -ToolName "WinUpdateReset" + } + finally { } } function WinReinstallStore { @@ -1788,8 +2197,7 @@ function WinReinstallStore { [int]$CountdownSeconds = 30, [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "WinReinstallStore" - Show-Header -SubTitle "Store Repair Toolkit" + Start-ToolkitSession -ToolName "WinReinstallStore" -SubTitle "Store Repair Toolkit" $savedProgressPref = $ProgressPreference $ProgressPreference = 'SilentlyContinue' function Install-MicrosoftStore { @@ -1801,12 +2209,7 @@ function WinReinstallStore { @( "$env:LOCALAPPDATA\Packages\Microsoft.WindowsStore_*\LocalCache", (Join-Path $env:LOCALAPPDATA "Microsoft\Windows\INetCache") - ) | ForEach-Object { - if (Test-Path $_) { - $ProgressPreference = 'SilentlyContinue' - Remove-Item $_ -Recurse -Force -ErrorAction SilentlyContinue *>$null - } - } + ) | ForEach-Object { Remove-ItemSafely -Path $_ -Recurse } $wingetExe = Get-WingetExecutable $installMethods = @( @{ @@ -1814,17 +2217,7 @@ function WinReinstallStore { Action = { if (-not (Test-Path $wingetExe -ErrorAction SilentlyContinue)) { return @{ ExitCode = -1 } } $processResult = Invoke-WithConsoleRedirection -Action { - Invoke-WithSpinner -Activity "Installazione Store tramite Winget" -Process -Action { - $procParams = @{ - FilePath = $wingetExe - ArgumentList = @('install', '9WZDNCRFJBMP', - '--accept-source-agreements', '--accept-package-agreements', - '--silent', '--disable-interactivity') - PassThru = $true - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds 300 + Invoke-WithSpinner -Activity "Installazione Store tramite Winget" -Command $wingetExe -Arguments @('install', '9WZDNCRFJBMP', '--accept-source-agreements', '--accept-package-agreements', '--silent', '--disable-interactivity') -TimeoutSeconds 300 -LogContextKey "Store-Winget-Install" } return @{ ExitCode = $processResult.ExitCode } } @@ -1845,15 +2238,7 @@ function WinReinstallStore { Name = 'DISM Capability' Action = { $result = Invoke-WithConsoleRedirection -Action { - Invoke-WithSpinner -Activity "Aggiunta Store via DISM" -Process -Action { - $procParams = @{ - FilePath = 'DISM' - ArgumentList = @('/Online', '/Add-Capability', '/CapabilityName:Microsoft.WindowsStore~~~~0.0.1.0') - PassThru = $true - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds 300 + Invoke-WithSpinner -Activity "Aggiunta Store via DISM" -Command 'DISM' -Arguments @('/Online', '/Add-Capability', '/CapabilityName:Microsoft.WindowsStore~~~~0.0.1.0') -TimeoutSeconds 300 -LogContextKey "Store-DISM-Add" } return @{ ExitCode = $result.ExitCode } } @@ -1864,8 +2249,7 @@ function WinReinstallStore { Write-StyledMessage -Type 'Info' -Text "Tentativo tramite: $($method.Name)." try { $result = $method.Action.Invoke() - $clearLine = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLine -NoNewline + Clear-ProgressLine [Console]::Out.Flush() $isSuccess = $result -and ($result.ExitCode -in @(0, 3010, 1638, -1978335189)) if ($isSuccess) { @@ -1883,17 +2267,9 @@ function WinReinstallStore { } if ($success) { $null = Invoke-WithConsoleRedirection -Action { - Invoke-WithSpinner -Activity "Reset cache Microsoft Store (wsreset)" -Process -Action { - $procParams = @{ - FilePath = 'wsreset.exe' - PassThru = $true - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds 120 + Invoke-WithSpinner -Activity "Reset cache Microsoft Store (wsreset)" -Command 'wsreset.exe' -TimeoutSeconds 120 -LogContextKey "Store-WSReset" } - $clearLine = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLine -NoNewline + Clear-ProgressLine [Console]::Out.Flush() Write-StyledMessage -Type 'Success' -Text "Cache dello Store ripristinata." } @@ -1907,8 +2283,7 @@ function WinReinstallStore { Start-AppxSilentProcess -AppxPath "$($_.InstallLocation)\AppXManifest.xml" -Flags '-DisableDevelopmentMode -Register -ForceApplicationShutdown' } } -TimeoutSeconds 300 - $clearLine = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLine -NoNewline + Clear-ProgressLine [Console]::Out.Flush() Write-StyledMessage -Type 'Success' -Text "Microsoft Store ripristinato tramite metodo di emergenza." $success = $true @@ -1927,42 +2302,32 @@ function WinReinstallStore { return $false } try { - $null = Invoke-WithSpinner -Activity "Disinstallazione versioni precedenti UniGet UI" -Process -Action { - $procParams = @{ - FilePath = $wingetExe - ArgumentList = @('uninstall', '--exact', '--id', 'MartiCliment.UniGetUI', - '--silent', '--disable-interactivity') - PassThru = $true - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds 120 - $clearLine = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLine -NoNewline - [Console]::Out.Flush() - $processResult = Invoke-WithSpinner -Activity "Installazione UniGet UI" -Process -Action { - $procParams = @{ - FilePath = $wingetExe - ArgumentList = @('install', '--exact', '--id', 'Devolutions.UniGetUI', - '--source', 'winget', '--accept-source-agreements', - '--accept-package-agreements', '--silent', - '--disable-interactivity', '--force') - PassThru = $true - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds 600 - $clearLine = "`r" + (' ' * ([Console]::WindowWidth - 1)) + "`r" - Write-Host $clearLine -NoNewline + foreach ($oldId in @('MartiCliment.UniGetUI', 'Devolutions.UniGetUI')) { + $null = Invoke-WithSpinner -Activity "Disinstallazione $oldId" -Command $wingetExe -Arguments @('uninstall', '--exact', '--id', $oldId, '--silent', '--disable-interactivity') -TimeoutSeconds 120 -LogContextKey "Store-UniGet-Uninstall" + Clear-ProgressLine + [Console]::Out.Flush() + } + $processResult = Invoke-WithSpinner -Activity "Installazione UniGet UI" -Command $wingetExe -Arguments @('install', '--exact', '--id', 'Devolutions.UniGetUI', '--source', 'winget', '--accept-source-agreements', '--accept-package-agreements', '--silent', '--disable-interactivity', '--force') -TimeoutSeconds 600 -LogContextKey "Store-UniGet-Install" + Clear-ProgressLine [Console]::Out.Flush() $isSuccess = $processResult.ExitCode -in @(0, 3010, 1638, -1978335189) if ($isSuccess) { Write-StyledMessage -Type 'Success' -Text "UniGet UI installato correttamente." try { $regPath = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' - if (Get-ItemProperty -Path $regPath -Name 'WingetUI' -ErrorAction SilentlyContinue) { - Remove-ItemProperty -Path $regPath -Name 'WingetUI' -ErrorAction SilentlyContinue *>$null - Write-StyledMessage -Type 'Success' -Text "Avvio automatico UniGet UI disabilitato." + foreach ($runName in @('WingetUI', 'UniGetUI', 'UniGet UI')) { + if (Get-ItemProperty -Path $regPath -Name $runName -ErrorAction SilentlyContinue) { + Remove-ItemProperty -Path $regPath -Name $runName -ErrorAction SilentlyContinue *>$null + Write-StyledMessage -Type 'Success' -Text "Avvio automatico '$runName' rimosso dal registro." + } + } + $startupFolder = [Environment]::GetFolderPath('Startup') + foreach ($lnkName in @('UniGetUI.lnk', 'WingetUI.lnk', 'UniGet UI.lnk')) { + $lnkPath = Join-Path $startupFolder $lnkName + if (Test-Path $lnkPath) { + Remove-Item $lnkPath -Force -ErrorAction SilentlyContinue *>$null + Write-StyledMessage -Type 'Success' -Text "Collegamento avvio automatico '$lnkName' rimosso." + } } } catch { } @@ -2032,9 +2397,9 @@ function WinReinstallStore { } $handlesRedirected = $false try { - [WinReinstallStore.NativeConsole]::SetStdHandle($STD_OUTPUT, $hNullOut) | Out-Null - [WinReinstallStore.NativeConsole]::SetStdHandle($STD_ERROR, $hNullOut) | Out-Null - [WinReinstallStore.NativeConsole]::SetStdHandle($STD_INPUT, $hNullIn) | Out-Null + [WinReinstallStore.NativeConsole]::SetStdHandle($STD_OUTPUT, $hNullOut) *>$null + [WinReinstallStore.NativeConsole]::SetStdHandle($STD_ERROR, $hNullOut) *>$null + [WinReinstallStore.NativeConsole]::SetStdHandle($STD_INPUT, $hNullIn) *>$null $handlesRedirected = $true $env:POWERSHELL_TELEMETRY_OPTOUT = '1' $ProgressPreference = 'SilentlyContinue' @@ -2043,17 +2408,17 @@ function WinReinstallStore { finally { if ($handlesRedirected) { try { - [WinReinstallStore.NativeConsole]::SetStdHandle($STD_OUTPUT, $hOrigOut) | Out-Null - [WinReinstallStore.NativeConsole]::SetStdHandle($STD_ERROR, $hOrigErr) | Out-Null - [WinReinstallStore.NativeConsole]::SetStdHandle($STD_INPUT, $hOrigIn) | Out-Null + [WinReinstallStore.NativeConsole]::SetStdHandle($STD_OUTPUT, $hOrigOut) *>$null + [WinReinstallStore.NativeConsole]::SetStdHandle($STD_ERROR, $hOrigErr) *>$null + [WinReinstallStore.NativeConsole]::SetStdHandle($STD_INPUT, $hOrigIn) *>$null } catch { } } if ($hNullOut -and $hNullOut -ne $INVALID_HANDLE_VALUE -and $hNullOut -ne [IntPtr]::Zero) { - try { [WinReinstallStore.NativeConsole]::CloseHandle($hNullOut) | Out-Null } catch { } + try { [WinReinstallStore.NativeConsole]::CloseHandle($hNullOut) *>$null } catch { } } if ($hNullIn -and $hNullIn -ne $INVALID_HANDLE_VALUE -and $hNullIn -ne [IntPtr]::Zero) { - try { [WinReinstallStore.NativeConsole]::CloseHandle($hNullIn) | Out-Null } catch { } + try { [WinReinstallStore.NativeConsole]::CloseHandle($hNullIn) *>$null } catch { } } } } @@ -2061,7 +2426,7 @@ function WinReinstallStore { Write-StyledMessage -Type 'Progress' -Text "Avvio reinstallazione Store & Winget." $wingetResult = $false try { - $global:ProgressPreference = 'SilentlyContinue' + $ProgressPreference = 'SilentlyContinue' $wingetResult = Invoke-WithConsoleRedirection -Action { Reset-Winget -Force } } catch { @@ -2069,7 +2434,7 @@ function WinReinstallStore { Write-ToolkitLog -Level ERROR -Message "Reset-Winget eccezione non gestita: $($_.Exception.Message)" } finally { - $global:ProgressPreference = $savedProgressPref + $ProgressPreference = $savedProgressPref } if ($wingetResult) { Write-StyledMessage -Type 'Success' -Text "Winget ripristinato e operativo." @@ -2096,26 +2461,15 @@ function WinReinstallStore { finally { $ProgressPreference = $savedProgressPref } - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - } - else { - if (Start-InterruptibleCountdown -Seconds $CountdownSeconds -Message "Riavvio in") { - Restart-Computer -Force - } - } + Invoke-ToolkitReboot -Message "Riavvio in" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot } function WinBackupDriver { [CmdletBinding()] param( - [Parameter(Mandatory = $false)] [int]$CountdownSeconds = 10, - [Parameter(Mandatory = $false)] [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "WinBackupDriver" - Show-Header -SubTitle "Driver Backup Toolkit" - $Host.UI.RawUI.WindowTitle = "Driver Backup Toolkit By MagnetarMan" + Start-ToolkitSession -ToolName "WinBackupDriver" -SubTitle "Driver Backup Toolkit" $timeout = 86400 $script:BackupConfig = @{ DateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" @@ -2126,1318 +2480,731 @@ function WinBackupDriver { LogsDir = $AppConfig.Paths.DriverBackupLogs } $script:FinalArchivePath = "$($script:BackupConfig.DesktopPath)\$($script:BackupConfig.ArchiveName)_$($script:BackupConfig.DateTime).7z" - function Test-AdministratorPrivilege { - $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = New-Object Security.Principal.WindowsPrincipal($currentIdentity) - return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - } function Initialize-BackupEnvironment { - Write-StyledMessage Info "🗂️ Inizializzazione ambiente backup." + Write-StyledMessage -Type 'Info' -Text "🗂️ Inizializzazione ambiente backup." try { if (Test-Path $script:BackupConfig.BackupDir) { - Write-StyledMessage Warning "Rimozione backup precedenti." - Remove-Item $script:BackupConfig.BackupDir -Recurse -Force -ErrorAction Stop | Out-Null + Write-StyledMessage -Type 'Warning' -Text "Rimozione backup precedenti." + Remove-ItemSafely -Path $script:BackupConfig.BackupDir -Recurse } - New-Item -ItemType Directory -Path $script:BackupConfig.BackupDir -Force | Out-Null - New-Item -ItemType Directory -Path $script:BackupConfig.LogsDir -Force | Out-Null - Write-StyledMessage Success "Directory backup e log create." + New-Item -ItemType Directory -Path $script:BackupConfig.BackupDir -Force *>$null + New-Item -ItemType Directory -Path $script:BackupConfig.LogsDir -Force *>$null + Write-StyledMessage -Type 'Success' -Text "Directory backup e log create." return $true } catch { - Write-StyledMessage Error "Errore inizializzazione ambiente: $_" + Write-StyledMessage -Type 'Error' -Text "Errore inizializzazione ambiente: $_" return $false } } function Export-SystemDrivers { - Write-StyledMessage Info "💾 Avvio esportazione driver di sistema." - $outFile = "$($script:BackupConfig.LogsDir)\dism_$($script:BackupConfig.DateTime).log" - $errFile = "$($script:BackupConfig.LogsDir)\dism_err_$($script:BackupConfig.DateTime).log" try { - $result = Invoke-WithSpinner -Activity "Esportazione driver DISM" -Process -Action { - $procParams = @{ - FilePath = 'dism.exe' - ArgumentList = @('/online', '/export-driver', "/destination:`"$($script:BackupConfig.BackupDir)`"") - NoNewWindow = $true - PassThru = $true - RedirectStandardOutput = $outFile - RedirectStandardError = $errFile - } - Start-Process @procParams - } -TimeoutSeconds $timeout -UpdateInterval 1000 - if ($result.TimedOut) { - throw "Timeout raggiunto durante l'esportazione DISM" - } - if ($result.ExitCode -ne 0) { - $errorDetails = if (Test-Path $errFile) { - (Get-Content $errFile -ErrorAction SilentlyContinue) -join '; ' - } - else { "Dettagli non disponibili" } - throw "Esportazione DISM fallita (ExitCode: $($result.ExitCode)). Dettagli: $errorDetails" - } + $result = Invoke-WithSpinner -Activity "Esportazione driver DISM" -Command 'dism.exe' ` + -Arguments @('/online', '/export-driver', "/destination:`"$($script:BackupConfig.BackupDir)`"") ` + -TimeoutSeconds $timeout -LogContextKey "Backup-DISM" + if ($result.TimedOut) { throw "Timeout raggiunto durante l'esportazione DISM" } + if ($result.ExitCode -ne 0) { throw "Esportazione DISM fallita con ExitCode: $($result.ExitCode)." } $exportedDrivers = Get-ChildItem -Path $script:BackupConfig.BackupDir -Recurse -File -ErrorAction SilentlyContinue if (-not $exportedDrivers -or $exportedDrivers.Count -eq 0) { - Write-StyledMessage Warning "Nessun driver di terze parti trovato da esportare." - Write-StyledMessage Info "💡 I driver integrati di Windows non vengono esportati." + Write-StyledMessage -Type 'Warning' -Text "Nessun driver di terze parti trovato da esportare." + Write-StyledMessage -Type 'Info' -Text "💡 I driver integrati di Windows non vengono esportati." return $true } - $totalSize = ($exportedDrivers | Measure-Object -Property Length -Sum).Sum - $totalSizeMB = [Math]::Round($totalSize / 1MB, 2) - Write-StyledMessage Success "Esportazione completata: $($exportedDrivers.Count) driver trovati ($totalSizeMB MB)." + $totalSizeMB = [Math]::Round(($exportedDrivers | Measure-Object -Property Length -Sum).Sum / 1MB, 2) + Write-StyledMessage -Type 'Success' -Text "Esportazione completata: $($exportedDrivers.Count) driver ($totalSizeMB MB)." return $true } catch { - Write-StyledMessage Error "Errore durante esportazione driver: $_" + Write-StyledMessage -Type 'Error' -Text "Errore durante esportazione driver: $_" return $false } } - function Resolve-7ZipExecutable { - return Install-7ZipPortable - } function Install-7ZipPortable { - $installDir = "$env:LOCALAPPDATA\WinToolkit\7zip" + $installDir = Join-Path $AppConfig.Paths.LocalAppData "WinToolkit\7zip" $executablePath = "$installDir\7zr.exe" if (Test-Path $executablePath) { - Write-StyledMessage Success "7-Zip portable già presente." + Write-StyledMessage -Type 'Success' -Text "7-Zip portable già presente." return $executablePath } - New-Item -ItemType Directory -Path $installDir -Force | Out-Null + New-Item -ItemType Directory -Path $installDir -Force *>$null $downloadSources = @( @{ Url = $AppConfig.URLs.GitHubAssetBaseUrl + "7zr.exe"; Name = "Repository MagnetarMan" }, - @{ Url = $AppConfig.URLs.SevenZipOfficial; Name = "Sito ufficiale 7-Zip" } + @{ Url = $AppConfig.URLs.SevenZipOfficial; Name = "Sito ufficiale 7-Zip" } ) foreach ($source in $downloadSources) { try { - Write-StyledMessage Info "⬇️ Download 7-Zip da: $($source.Name)" + Write-StyledMessage -Type 'Info' -Text "⬇️ Download 7-Zip da: $($source.Name)" Invoke-WebRequest -Uri $source.Url -OutFile $executablePath -UseBasicParsing -ErrorAction Stop if (Test-Path $executablePath) { $fileSize = (Get-Item $executablePath).Length if ($fileSize -gt 100KB -and $fileSize -lt 10MB) { $testResult = & $executablePath 2>&1 if ($testResult -match "7-Zip" -or $testResult -match "Licensed") { - Write-StyledMessage Success "7-Zip portable scaricato e verificato." + Write-StyledMessage -Type 'Success' -Text "7-Zip portable scaricato e verificato." return $executablePath } } - Write-StyledMessage Warning "File scaricato non valido (Dimensione: $fileSize bytes)." - Remove-Item $executablePath -Force -ErrorAction SilentlyContinue + Write-StyledMessage -Type 'Warning' -Text "File scaricato non valido (Dimensione: $fileSize bytes)." + Remove-ItemSafely -Path $executablePath } } catch { - Write-StyledMessage Warning "Download fallito da $($source.Name): $_" - if (Test-Path $executablePath) { - Remove-Item $executablePath -Force -ErrorAction SilentlyContinue - } + Write-StyledMessage -Type 'Warning' -Text "Download fallito da $($source.Name): $_" + Remove-ItemSafely -Path $executablePath } } - Write-StyledMessage Error "Impossibile scaricare 7-Zip da tutte le fonti." + Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare 7-Zip da tutte le fonti." return $null } function Compress-BackupArchive { param([string]$SevenZipPath) - if (-not $SevenZipPath -or -not (Test-Path $SevenZipPath)) { - throw "Percorso 7-Zip non valido: $SevenZipPath" - } - if (-not (Test-Path $script:BackupConfig.BackupDir)) { - throw "Directory backup non trovata: $($script:BackupConfig.BackupDir)" - } - Write-StyledMessage Info "📦 Preparazione compressione archivio." + if (-not $SevenZipPath -or -not (Test-Path $SevenZipPath)) { throw "Percorso 7-Zip non valido: $SevenZipPath" } + if (-not (Test-Path $script:BackupConfig.BackupDir)) { throw "Directory backup non trovata: $($script:BackupConfig.BackupDir)" } + Write-StyledMessage -Type 'Info' -Text "📦 Preparazione compressione archivio." $backupFiles = Get-ChildItem -Path $script:BackupConfig.BackupDir -Recurse -File -ErrorAction SilentlyContinue if (-not $backupFiles) { - Write-StyledMessage Warning "Nessun file da comprimere nella directory backup." + Write-StyledMessage -Type 'Warning' -Text "Nessun file da comprimere nella directory backup." return $null } $totalSizeMB = [Math]::Round(($backupFiles | Measure-Object -Property Length -Sum).Sum / 1MB, 2) - Write-StyledMessage Info "Dimensione totale: $totalSizeMB MB" - $archivePath = "$($script:BackupConfig.TempPath)\$($script:BackupConfig.ArchiveName)_$($script:BackupConfig.DateTime).7z" + Write-StyledMessage -Type 'Info' -Text "Dimensione totale: $totalSizeMB MB" + $archivePath = "$($script:BackupConfig.TempPath)\$($script:BackupConfig.ArchiveName)_$($script:BackupConfig.DateTime).7z" $compressionArgs = @('a', '-t7z', '-mx=6', '-mmt=on', "`"$archivePath`"", "`"$($script:BackupConfig.BackupDir)\*`"") - $stdOutputPath = "$($script:BackupConfig.LogsDir)\7zip_$($script:BackupConfig.DateTime).log" - $stdErrorPath = "$($script:BackupConfig.LogsDir)\7zip_err_$($script:BackupConfig.DateTime).log" - try { - Write-StyledMessage Info "🚀 Compressione con 7-Zip." - $result = Invoke-WithSpinner -Activity "Compressione archivio 7-Zip" -Process -Action { - $procParams = @{ - FilePath = $SevenZipPath - ArgumentList = $compressionArgs - NoNewWindow = $true - PassThru = $true - RedirectStandardOutput = $stdOutputPath - RedirectStandardError = $stdErrorPath - } - Start-Process @procParams - } -TimeoutSeconds 800 -UpdateInterval 1000 - if ($result.TimedOut) { - throw "Timeout raggiunto durante la compressione." - } - if ($result.ExitCode -eq 0 -and (Test-Path $archivePath)) { - $compressedSizeMB = [Math]::Round((Get-Item $archivePath).Length / 1MB, 2) - $compressionRatio = [Math]::Round((1 - $compressedSizeMB / $totalSizeMB) * 100, 1) - Write-StyledMessage Success "Compressione completata: $compressedSizeMB MB (Riduzione: $compressionRatio%)." - return $archivePath - } - else { - $errorDetails = if (Test-Path $stdErrorPath) { - $errorContent = Get-Content $stdErrorPath -ErrorAction SilentlyContinue - if ($errorContent) { $errorContent -join '; ' } else { "Log errori vuoto." } - } - else { "File di log errori non trovato." } - Write-StyledMessage Error "Compressione fallita (ExitCode: $($result.ExitCode)). Dettagli: $errorDetails" - return $null - } - } - finally { - } + Write-StyledMessage -Type 'Info' -Text "🚀 Compressione con 7-Zip." + $result = Invoke-WithSpinner -Activity "Compressione archivio 7-Zip" -Command $SevenZipPath ` + -Arguments $compressionArgs -TimeoutSeconds 800 -LogContextKey "Backup-7Zip" + if ($result.TimedOut) { throw "Timeout raggiunto durante la compressione." } + if ($result.ExitCode -eq 0 -and (Test-Path $archivePath)) { + $compressedSizeMB = [Math]::Round((Get-Item $archivePath).Length / 1MB, 2) + $compressionRatio = [Math]::Round((1 - $compressedSizeMB / $totalSizeMB) * 100, 1) + Write-StyledMessage -Type 'Success' -Text "Compressione completata: $compressedSizeMB MB (Riduzione: $compressionRatio%)." + return $archivePath + } + Write-StyledMessage -Type 'Error' -Text "Compressione fallita con ExitCode: $($result.ExitCode)." + return $null } function Move-ArchiveToDesktop { param([string]$ArchivePath) if ([string]::IsNullOrWhiteSpace($ArchivePath) -or -not (Test-Path $ArchivePath)) { throw "Percorso archivio non valido: $ArchivePath" } - Write-StyledMessage Info "📂 Spostamento archivio su desktop." + Write-StyledMessage -Type 'Info' -Text "📂 Spostamento archivio su desktop." try { if (-not (Test-Path $script:BackupConfig.DesktopPath)) { throw "Directory desktop non accessibile: $($script:BackupConfig.DesktopPath)" } if (Test-Path $script:FinalArchivePath) { - Write-StyledMessage Warning "Rimozione archivio precedente." - Remove-Item $script:FinalArchivePath -Force -ErrorAction Stop + Write-StyledMessage -Type 'Warning' -Text "Rimozione archivio precedente." + Remove-ItemSafely -Path $script:FinalArchivePath } Copy-Item -Path $ArchivePath -Destination $script:FinalArchivePath -Force -ErrorAction Stop if (Test-Path $script:FinalArchivePath) { - Write-StyledMessage Success "Archivio salvato sul desktop." - Write-StyledMessage Info "Posizione: $script:FinalArchivePath" + Write-StyledMessage -Type 'Success' -Text "Archivio salvato sul desktop." + Write-StyledMessage -Type 'Info' -Text "Posizione: $script:FinalArchivePath" return $true } throw "Copia archivio fallita." } catch { - Write-StyledMessage Error "Errore spostamento archivio: $_" + Write-StyledMessage -Type 'Error' -Text "Errore spostamento archivio: $_" return $false } } try { - if (-not (Test-AdministratorPrivilege)) { - Write-StyledMessage Error "❌ Privilegi amministratore richiesti." - Write-StyledMessage Info "💡 Riavvia PowerShell come Amministratore." - Read-Host "`nPremi INVIO per uscire" - return - } - Write-StyledMessage Info "🚀 Inizializzazione sistema." + Write-StyledMessage -Type 'Info' -Text "🚀 Inizializzazione sistema." Start-Sleep -Seconds 1 - if (Initialize-BackupEnvironment) { - Write-Host "" - if (Export-SystemDrivers) { - Write-Host "" - $sevenZipPath = (Resolve-7ZipExecutable | Select-Object -Last 1) - if ($sevenZipPath) { - Write-Host "" - $compressedArchive = Compress-BackupArchive -SevenZipPath $sevenZipPath - if ($compressedArchive) { - Write-Host "" - if (Move-ArchiveToDesktop -ArchivePath $compressedArchive) { - Write-Host "" - Write-StyledMessage Success "🎉 Backup driver completato con successo!" - Write-StyledMessage Info "📁 Archivio finale: $script:FinalArchivePath" - Write-StyledMessage Info "💾 Utilizzabile per reinstallare tutti i driver." - Write-StyledMessage Info "🔧 Senza doverli riscaricare singolarmente." - } - } - } - } + if (-not (Initialize-BackupEnvironment)) { return } + if (-not (Export-SystemDrivers)) { return } + $sevenZipPath = Install-7ZipPortable | Select-Object -Last 1 + if (-not $sevenZipPath) { return } + $compressedArchive = Compress-BackupArchive -SevenZipPath $sevenZipPath + if (-not $compressedArchive) { return } + if (Move-ArchiveToDesktop -ArchivePath $compressedArchive) { + Write-StyledMessage -Type 'Success' -Text "🎉 Backup driver completato con successo!" + Write-StyledMessage -Type 'Info' -Text "📁 Archivio finale: $script:FinalArchivePath" + Write-StyledMessage -Type 'Info' -Text "💾 Utilizzabile per reinstallare tutti i driver senza riscaricarli." } } catch { - Write-StyledMessage Error "Errore critico durante backup: $($_.Exception.Message)" - Write-StyledMessage Info "💡 Controlla i log per dettagli tecnici." - Write-ToolkitLog -Level ERROR -Message "Errore critico in WinBackupDriver" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace - } + Write-ToolkitError -Record $_ -ToolName "WinBackupDriver" -Message "Errore critico durante backup" + Write-StyledMessage -Type 'Info' -Text "💡 Controlla i log per dettagli tecnici." } finally { - Write-StyledMessage Info "🧹 Pulizia ambiente temporaneo." - if (Test-Path $script:BackupConfig.BackupDir) { - Remove-Item $script:BackupConfig.BackupDir -Recurse -Force -ErrorAction SilentlyContinue - } + Write-StyledMessage -Type 'Info' -Text "🧹 Pulizia ambiente temporaneo." + Remove-ItemSafely -Path $script:BackupConfig.BackupDir -Recurse + Write-StyledMessage -Type 'Success' -Text "🎯 Driver Backup Toolkit terminato." Write-ToolkitLog -Level INFO -Message "WinBackupDriver sessione terminata." - Write-StyledMessage Success "🎯 Driver Backup Toolkit terminato." } } function WinDriverInstall {} -function OfficeToolkit { +function WinDebloat { [CmdletBinding()] param( [int]$CountdownSeconds = 30, [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "OfficeToolkit" - Show-Header -SubTitle "Office Toolkit" - $Host.UI.RawUI.WindowTitle = "Office Toolkit By MagnetarMan" - $tempDir = $AppConfig.Paths.OfficeTemp - function Invoke-SilentRemoval { - param( - [Parameter(Mandatory = $true)] - [string]$Path, - [switch]$Recurse - ) - if (-not (Test-Path $Path)) { + Start-ToolkitSession -ToolName "WinDebloat" -SubTitle "WinDebloat Toolkit" + $DebloatServices = @( + ) + $rebootRequired = $false + function Invoke-ServiceOptimization { + param([hashtable]$ServiceConfig) + Write-StyledMessage -Type 'Info' -Text "Ottimizzazione servizio: $($ServiceConfig.Name) ($($ServiceConfig.Description))." + try { + Write-StyledMessage -Type 'Success' -Text "Servizio $($ServiceConfig.Name) ottimizzato correttamente." + return $true + } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante l'ottimizzazione di $($ServiceConfig.Name): $($_.Exception.Message)." return $false } - try { - $removeParams = @{ - Path = $Path - Force = $true - ErrorAction = 'SilentlyContinue' - } - if ($Recurse) { - $removeParams.Add('Recurse', $Recurse) - } - Remove-Item @removeParams *>$null - Clear-ProgressLine - return $true - } catch { - return $false + } + try { + Write-StyledMessage -Type 'Info' -Text "🚀 Avvio processo di debloat dei servizi." + foreach ($service in $DebloatServices) { Invoke-ServiceOptimization -ServiceConfig $service } + Write-StyledMessage -Type 'Success' -Text "✅ Operazioni di debloat completate." + if ($rebootRequired) { + Invoke-ToolkitReboot -Message "Riavvio per applicare le modifiche" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot } } - function Apply-OfficePostConfig { - Write-StyledMessage -Type 'Info' -Text "⚙️ Configurazione post-installazione/riparazione Office." - $telemetryKeys = @( - @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 }, - @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "disconnectedstate"; Value = 1 }, - @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "usercontentdisabled"; Value = 1 }, - @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "downloadcontentdisabled"; Value = 1 }, - @{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 } - ) - foreach ($reg in $telemetryKeys) { - if (-not (Test-Path $reg.Path)) { - $null = New-Item -Path $reg.Path -Force - } - $regParams = @{ - Path = $reg.Path - Name = $reg.Name - Value = $reg.Value - Type = 'DWord' - Force = $true - } - Set-ItemProperty @regParams - } - $regPathFeedback = "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\General" - if (-not (Test-Path $regPathFeedback)) { - $null = New-Item $regPathFeedback -Force - } - $feedbackParams = @{ - Path = $regPathFeedback - Name = "ShownOptIn" - Value = 1 - Type = 'DWord' - Force = $true - } - Set-ItemProperty @feedbackParams - Write-StyledMessage -Type 'Success' -Text "✅ Telemetria e Privacy Office disabilitate in modo profondo." - } - function Get-UserConfirmation { - [CmdletBinding()] + catch { + Write-ToolkitError -Record $_ -ToolName "WinDebloat" + } + finally { + Write-StyledMessage -Type 'Info' -Text "♻️ Pulizia risorse e chiusura sessione WinDebloat." + Write-ToolkitLog -Level INFO -Message "WinDebloat sessione terminata." + } +} +function WinCleaner { + [CmdletBinding()] + param( + [ValidateRange(0, 300)] + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + $script:WinCleanerLog = @() + function Add-CleanerLog { param( - [Parameter(Mandatory = $true)] - [string]$Message, - [ValidateSet('Y', 'N')] - [string]$DefaultChoice = 'N' + [Parameter(Mandatory = $true, Position = 0)] + [ValidateSet('Success', 'Info', 'Warning', 'Error', 'Question')] + [string]$Type, + [Parameter(Mandatory = $true, Position = 1)] + [string]$Text ) - do { - $response = (Read-Host "$Message [Y/N]").Trim().ToUpper() - if ($response -eq 'N') { - Write-StyledMessage -Type 'Warning' -Text "Inserire Y per confermare." - } elseif ($response -ne 'Y') { - Write-StyledMessage -Type 'Error' -Text "Input non valido." - } - } while ($response -ne 'Y') - return $response - } - function Get-WindowsVersion { - try { - $osInfo = Get-CimInstance -ClassName Win32_OperatingSystem - $buildNumber = [int]$osInfo.BuildNumber - return $buildNumber -ge 22631 ? "Windows11_23H2_Plus" : ($buildNumber -ge 22000 ? "Windows11_22H2_Or_Older" : "Windows10_Or_Older") - } - catch { - Write-StyledMessage -Type 'Warning' -Text "Impossibile rilevare versione Windows: $_" - return "Unknown" + Clear-ProgressLine + $script:WinCleanerLog += @{ + Timestamp = Get-Date -Format "HH:mm:ss" + Type = $Type + Text = $Text } + Write-StyledMessage -Type $Type -Text $Text } - function Stop-OfficeProcesses { - $processes = @('winword', 'excel', 'powerpnt', 'outlook', 'onenote', 'msaccess', 'visio', 'lync') - $closed = 0 - Write-StyledMessage -Type 'Info' -Text "📋 Chiusura processi Office." - foreach ($processName in $processes) { - $runningProcesses = Get-Process -Name $processName -ErrorAction SilentlyContinue - if ($runningProcesses) { - try { - $runningProcesses | Stop-Process -Force -ErrorAction Stop - $closed++ - } - catch { - Write-StyledMessage -Type 'Warning' -Text "Impossibile chiudere: $processName." + Start-ToolkitSession -ToolName "WinCleaner" -SubTitle "Cleaner Toolkit" + $timeout = 86400 + $ProgressPreference = 'Continue' + $VitalExclusions = @( + "$env:LOCALAPPDATA\WinToolkit" + ) + function Test-VitalExclusion { + param([string]$Path) + if ([string]::IsNullOrWhiteSpace($Path)) { return $false } + $fullPath = $Path -replace '"', '' + try { + if (-not [System.IO.Path]::IsPathRooted($fullPath)) { + $fullPath = Join-Path (Get-Location) $fullPath + } + foreach ($excluded in $VitalExclusions) { + if ($fullPath -like "$excluded*" -or $fullPath -eq $excluded) { + Add-CleanerLog -Type 'Info' -Text "🛡️ PROTEZIONE VITALE ATTIVATA: $fullPath" + return $true } } } - if ($closed -gt 0) { - Write-StyledMessage -Type 'Success' -Text "$closed processi Office chiusi." - } + catch { return $false } + return $false } - function Invoke-DownloadFile([string]$Url, [string]$OutputPath, [string]$Description) { + function Invoke-CommandAction { + param($Rule) + Clear-ProgressLine + Write-StyledMessage -Type 'Info' -Text "🚀 Esecuzione comando: $($Rule.Name)." try { - Write-StyledMessage -Type 'Info' -Text "📥 Download $Description." - $webClient = New-Object System.Net.WebClient - $webClient.DownloadFile($Url, $OutputPath) - $webClient.Dispose() - $success = (Test-Path $OutputPath) - Write-StyledMessage -Type ($success ? 'Success' : 'Error') -Text ($success ? "Download completato: $Description" : "File non trovato dopo download: $Description.") - return $success + $result = Invoke-WithSpinner -Activity $Rule.Name -Command $Rule.Command -Arguments $Rule.Args -TimeoutSeconds $timeout -LogContextKey "Cleaner-$($Rule.Name)" + if ($result.TimedOut) { + Write-StyledMessage -Type 'Warning' -Text "Comando timeout dopo $($timeout/3600) ore." + return $true + } + if ($result.ExitCode -eq -2146498554 -or $result.ExitCode -eq 0x800F0818) { + Add-CleanerLog -Type 'Warning' -Text "ATTENZIONE! - Stai effettuando la pulizia con Windows Update in corso. Aggiorna il sistema e riprova per eseguire la pulizia completa" + return $false + } + $isSuccess = ($result.ExitCode -eq 0) + Add-CleanerLog -Type ($isSuccess ? 'Info' : 'Warning') -Text ($isSuccess ? "Comando completato." : "Comando completato con codice $($result.ExitCode)") + return $true } catch { - Write-StyledMessage -Type 'Error' -Text "Errore download $Description`: $_" + Add-CleanerLog -Type 'Error' -Text "Errore comando: $_" return $false } } - function Start-OfficeInstallation { - Write-StyledMessage -Type 'Info' -Text "🏢 Avvio installazione Office Basic." + function Invoke-ServiceAction { + param($Rule) + $svcName = $Rule.ServiceName + $action = $Rule.Action try { - if (-not (Test-Path $tempDir)) { - $null = New-Item -ItemType Directory -Path $tempDir -Force - } - $setupPath = Join-Path $tempDir 'Setup.exe' - $configPath = Join-Path $tempDir 'Basic.xml' - $downloads = @( - @{ Url = $AppConfig.URLs.OfficeSetup; Path = $setupPath; Name = 'Setup Office' }, - @{ Url = $AppConfig.URLs.OfficeBasicConfig; Path = $configPath; Name = 'Configurazione Basic' } - ) - foreach ($download in $downloads) { - if (-not (Invoke-DownloadFile $download.Url $download.Path $download.Name)) { - return $false - } + $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue + if (-not $svc) { return $true } + if ($action -eq 'Stop' -and $svc.Status -eq 'Running') { + Add-CleanerLog -Type 'Info' -Text "⏸️ Arresto servizio $svcName." + Stop-Service -Name $svcName -Force -ErrorAction Stop *>$null } - Write-StyledMessage -Type 'Info' -Text "🚀 Avvio processo installazione." - $arguments = "/configure `"$configPath`"" - $processTimeoutSeconds = 86400 - $result = Invoke-WithSpinner -Activity "Installazione Office Basic" -Process -Action { - $procParams = @{ - FilePath = $setupPath - ArgumentList = $arguments - WorkingDirectory = $tempDir - PassThru = $true - WindowStyle = 'Hidden' - ErrorAction = 'Stop' - } - Start-Process @procParams - } -TimeoutSeconds $processTimeoutSeconds -UpdateInterval 1000 - if (-not $result.Success) { - Write-StyledMessage -Type 'Error' -Text "Installazione fallita o scaduta (fase di setup iniziale)." - return $false + elseif ($action -eq 'Start' -and $svc.Status -ne 'Running') { + Add-CleanerLog -Type 'Info' -Text "▶️ Avvio servizio $svcName." + Start-Service -Name $svcName -ErrorAction Stop *>$null } - Apply-OfficePostConfig - Write-StyledMessage -Type 'Success' -Text "Installazione completata." - Write-StyledMessage -Type 'Info' -Text "Riavvio non necessario." return $true } catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante installazione Office: $($_.Exception.Message)" + Add-CleanerLog -Type 'Warning' -Text "Errore servizio $svcName : $_" return $false } - finally { - Invoke-SilentRemoval -Path $tempDir -Recurse - } } - function Start-OfficeRepair { - Write-StyledMessage -Type 'Info' -Text "🔧 Avvio riparazione Office." - Stop-OfficeProcesses - Write-StyledMessage -Type 'Info' -Text "🧹 Pulizia cache Office." - $caches = @( - "$env:LOCALAPPDATA\Microsoft\Office\16.0\Lync\Lync.cache", - "$env:LOCALAPPDATA\Microsoft\Office\16.0\OfficeFileCache" - ) - $cleanedCount = 0 - foreach ($cache in $caches) { - if (Invoke-SilentRemoval -Path $cache -Recurse) { - $cleanedCount++ + function Remove-FileItem { + param($Rule) + $paths = $Rule.Paths + $isPerUser = $Rule.PerUser + $filesOnly = $Rule.FilesOnly + $takeOwn = $Rule.TakeOwnership + $targetPaths = @() + if ($isPerUser) { + $users = Get-LocalUserProfiles + foreach ($user in $users) { + foreach ($p in $paths) { + $targetPaths += $p -replace '%USERPROFILE%', $user.FullName ` + -replace '%APPDATA%', "$($user.FullName)\AppData\Roaming" ` + -replace '%LOCALAPPDATA%', "$($user.FullName)\AppData\Local" ` + -replace '%TEMP%', "$($user.FullName)\AppData\Local\Temp" + } } } - if ($cleanedCount -gt 0) { - Write-StyledMessage -Type 'Success' -Text "$cleanedCount cache eliminate." - } - $officeClient = (Test-Path "${env:ProgramFiles}\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe") ? "${env:ProgramFiles}\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe" : "${env:ProgramFiles(x86)}\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe" - if (-not (Test-Path $officeClient)) { - Write-StyledMessage -Type 'Error' -Text "OfficeClickToRun.exe non trovato. Office potrebbe non essere installato." - return $false - } - try { - $processTimeoutSeconds = 86400 - Write-StyledMessage -Type 'Info' -Text "🔧 Avvio riparazione rapida (offline)." - $argumentsQuick = "scenario=Repair platform=x64 culture=it-it forceappshutdown=True RepairType=QuickRepair DisplayLevel=True" - $resultQuick = Invoke-WithSpinner -Activity "Riparazione Rapida Office (Offline)" -Process -Action { - $procParams = @{ - FilePath = $officeClient - ArgumentList = $argumentsQuick - PassThru = $true - ErrorAction = 'Stop' - } - return Start-Process @procParams - } -TimeoutSeconds $processTimeoutSeconds -UpdateInterval 1000 - Apply-OfficePostConfig - Write-StyledMessage -Type 'Success' -Text "🎉 Riparazione Office completata!" - return $true + else { + foreach ($p in $paths) { $targetPaths += [Environment]::ExpandEnvironmentVariables($p) } } - catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante riparazione Office: $($_.Exception.Message)." + $count = 0 + foreach ($path in $targetPaths) { + if (Test-VitalExclusion $path) { continue } + if (-not (Test-Path $path)) { continue } try { - Write-StyledMessage -Type 'Info' -Text "🌐 Tentativo riparazione completa (online) come fallback." - $processTimeoutSeconds = 86400 - $argumentsFull = "scenario=Repair platform=x64 culture=it-it forceappshutdown=True RepairType=FullRepair DisplayLevel=True" - $resultFull = Invoke-WithSpinner -Activity "Riparazione Completa Office (Online)" -Process -Action { - $procParams = @{ - FilePath = $officeClient - ArgumentList = $argumentsFull - PassThru = $true - ErrorAction = 'Stop' + if ($takeOwn) { + Add-CleanerLog -Type 'Info' -Text "🔑 Assunzione proprietà per $path." + $null = & cmd /c "takeown /F `"$path`" /R /A >nul 2>&1" + $adminSID = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544') + $adminAccount = $adminSID.Translate([System.Security.Principal.NTAccount]).Value + $null = & cmd /c "icacls `"$path`" /T /grant `"${adminAccount}:F`" >nul 2>&1" + } + if ($filesOnly) { + $files = Get-ChildItem -Path $path -File -Force -ErrorAction SilentlyContinue + foreach ($file in $files) { + Remove-Item -Path $file.FullName -Force -ErrorAction Stop } - return Start-Process @procParams - } -TimeoutSeconds $processTimeoutSeconds -UpdateInterval 1000 - Apply-OfficePostConfig - Write-StyledMessage -Type 'Success' -Text "🎉 Riparazione Office completata!" - return $true + } + else { + Remove-Item -Path $path -Recurse -Force -ErrorAction Stop + } + $count++ } catch { - Write-StyledMessage -Type 'Error' -Text "Errore anche durante riparazione online: $($_.Exception.Message)." - return $false + Add-CleanerLog -Type 'Warning' -Text "Errore rimozione $path : $_" } } + if ($count -gt 0) { Write-StyledMessage -Type 'Success' -Text "🗑️ Puliti $count elementi in $($Rule.Name)." } + return $true } - function Remove-ItemsSilently { - param( - [string[]]$Paths, - [string]$ItemType = "cartella" - ) - $removed = @() - $failed = @() - foreach ($path in $Paths) { - if (Test-Path $path) { - if (Invoke-SilentRemoval -Path $path -Recurse) { - $removed += $path + function Remove-RegistryItem { + param($Rule) + $keys = $Rule.Keys + $recursive = $Rule.Recursive + $valuesOnly = $Rule.ValuesOnly + foreach ($rawKey in $keys) { + $key = $rawKey -replace '^(HKCU|HKLM):\\*', '$1:\' + if (-not (Test-Path $key)) { continue } + try { + if ($valuesOnly) { + $item = Get-Item $key -ErrorAction Stop + $item.GetValueNames() | ForEach-Object { + if ($_ -ne '(default)') { Remove-ItemProperty -LiteralPath $key -Name $_ -Force -ErrorAction SilentlyContinue *>$null } + } + if ($recursive) { + Get-ChildItem $key -Recurse -ErrorAction SilentlyContinue | ForEach-Object { + $currentKeyPath = $_.PSPath + $_.GetValueNames() | ForEach-Object { Remove-ItemProperty -LiteralPath $currentKeyPath -Name $_ -Force -ErrorAction SilentlyContinue *>$null } + } + } + Add-CleanerLog -Type 'Success' -Text "⚙️ Puliti valori in $key" } else { - $failed += $path + Remove-Item -Path $key -Recurse:$recursive -Force -ErrorAction Stop + Add-CleanerLog -Type 'Success' -Text "🗑️ Rimossa chiave $key" } } + catch { + Add-CleanerLog -Type 'Warning' -Text "Errore registro $key : $_" + } } - return @{ - Removed = $removed - Failed = $failed - Count = $removed.Count - } + return $true } - function Remove-OfficeDirectly { - Write-StyledMessage -Type 'Info' -Text "🔧 Avvio rimozione diretta Office." + function Set-RegistryItem { + param($Rule) + $key = $Rule.Key -replace '^(HKCU|HKLM):', '$1:\' try { - Write-StyledMessage -Type 'Info' -Text "📋 Ricerca installazioni Office." - $officePackages = Get-Package -ErrorAction SilentlyContinue | - Where-Object { $_.Name -like "*Microsoft Office*" -or $_.Name -like "*Microsoft 365*" -or $_.Name -like "*Office*" } - if ($officePackages) { - Write-StyledMessage -Type 'Info' -Text "Trovati $($officePackages.Count) pacchetti Office." - foreach ($package in $officePackages) { - try { - $null = Uninstall-Package -Name $package.Name -Force -ErrorAction Stop - Write-StyledMessage -Type 'Success' -Text "Rimosso: $($package.Name)." - } - catch {} - } - } - Write-StyledMessage -Type 'Info' -Text "🔍 Ricerca nel registro." - $uninstallKeys = @( - "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", - "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*", - "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" - ) - foreach ($keyPath in $uninstallKeys) { - try { - $items = Get-ItemProperty -Path $keyPath -ErrorAction SilentlyContinue | - Where-Object { $_.DisplayName -like "*Office*" -or $_.DisplayName -like "*Microsoft 365*" } - foreach ($item in $items) { - if ($item.UninstallString -and $item.UninstallString -match "msiexec") { - try { - $productCode = $item.PSChildName - $spinnerActivity = "Rimozione: $($item.DisplayName)" - $null = Invoke-WithSpinner -Activity $spinnerActivity -Process -Action { - $procParams = @{ - FilePath = 'msiexec.exe' - ArgumentList = @('/x', $productCode, '/qn', '/norestart') - PassThru = $true - WindowStyle = 'Hidden' - ErrorAction = 'Stop' - } - Start-Process @procParams - } -TimeoutSeconds 1800 -UpdateInterval 1000 - } - catch {} - } - } + Set-RegistryValue -Path $key -Name $Rule.ValueName -Value $Rule.ValueData -Type $Rule.ValueType + Add-CleanerLog -Type 'Success' -Text "⚙️ Impostato $key\$($Rule.ValueName)" + return $true + } + catch { return $false } + } + function Invoke-WinCleanerRule { + param($Rule) + Clear-ProgressLine + switch ($Rule.Type) { + 'File' { return Remove-FileItem -Rule $Rule } + 'Registry' { return Remove-RegistryItem -Rule $Rule } + 'RegSet' { return Set-RegistryItem -Rule $Rule } + 'Service' { return Invoke-ServiceAction -Rule $Rule } + 'Command' { return Invoke-CommandAction -Rule $Rule } + 'ScriptBlock' { + if ($Rule.ScriptBlock) { + & $Rule.ScriptBlock + return $true } - catch {} } - Write-StyledMessage -Type 'Info' -Text "🛑 Arresto servizi Office." - $officeServices = @('ClickToRunSvc', 'OfficeSvc', 'OSE') - $stoppedServices = 0 - foreach ($serviceName in $officeServices) { - $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue - if ($service) { - try { - Stop-Service -Name $serviceName -Force -ErrorAction Stop - Set-Service -Name $serviceName -StartupType Disabled -ErrorAction Stop - Write-StyledMessage -Type 'Success' -Text "Servizio arrestato: $serviceName." - $stoppedServices++ - } - catch {} + 'Custom' { + if ($Rule.ScriptBlock) { + & $Rule.ScriptBlock + return $true } } - Write-StyledMessage -Type 'Info' -Text "🧹 Pulizia cartelle Office." - $foldersToClean = @( - "$env:ProgramFiles\Microsoft Office", - "${env:ProgramFiles(x86)}\Microsoft Office", - "$env:ProgramFiles\Microsoft Office 15", - "${env:ProgramFiles(x86)}\Microsoft Office 15", - "$env:ProgramFiles\Microsoft Office 16", - "${env:ProgramFiles(x86)}\Microsoft Office 16", - "$env:ProgramData\Microsoft\Office", - "$env:LOCALAPPDATA\Microsoft\Office", - "$env:ProgramFiles\Common Files\Microsoft Shared\ClickToRun", - "${env:ProgramFiles(x86)}\Common Files\Microsoft Shared\ClickToRun" - ) - $folderResult = Remove-ItemsSilently -Paths $foldersToClean -ItemType "cartella" - if ($folderResult.Count -gt 0) { - Write-StyledMessage -Type 'Success' -Text "$($folderResult.Count) cartelle Office rimosse." - } - if ($folderResult.Failed.Count -gt 0) { - Write-StyledMessage -Type 'Warning' -Text "Impossibile rimuovere $($folderResult.Failed.Count) cartelle (potrebbero essere in uso)." - } - Write-StyledMessage -Type 'Info' -Text "🔧 Pulizia registro Office." - $registryPaths = @( - "HKCU:\Software\Microsoft\Office", - "HKLM:\SOFTWARE\Microsoft\Office", - "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Office", - "HKCU:\Software\Microsoft\Office\16.0", - "HKLM:\SOFTWARE\Microsoft\Office\16.0", - "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun", - "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Office\ClickToRun" - ) - $regResult = Remove-ItemsSilently -Paths $registryPaths -ItemType "chiave" - if ($regResult.Count -gt 0) { - Write-StyledMessage -Type 'Success' -Text "$($regResult.Count) chiavi registro Office rimosse." + } + return $true + } + $Rules = @( + @{ Name = "CleanMgr Config"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🧹 Configurazione CleanMgr." + $reg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches" + $opts = @( + "Active Setup Temp Folders", + "BranchCache", + "D3D Shader Cache", + "Delivery Optimization Files", + "Device Driver Packages", + "Downloaded Program Files", + "Internet Cache Files", + "Memory Dump Files", + "Old ChkDsk Files", + "Recycle Bin", + "Temporary Files", + "Thumbnail Cache", + "Update Cleanup", + "Windows Defender", + "Windows Error Reporting Files", + "Setup Log Files", + "System error memory dump files", + "System error minidump files", + "Temporary Setup Files", + "Windows Upgrade Log Files" + ) + foreach ($o in $opts) { + $p = Join-Path $reg $o + if (Test-Path $p) { Set-ItemProperty -Path $p -Name "StateFlags0065" -Value 2 -Type DWORD -Force -ErrorAction SilentlyContinue } + } + $cleanMgrExecutionRule = @{ + Name = "Esecuzione CleanMgr con /sagerun:65"; + Type = "Command"; + Command = "cleanmgr.exe"; + Args = @("/sagerun:65"); + } + Invoke-CommandAction -Rule $cleanMgrExecutionRule + Add-CleanerLog -Type 'Info' -Text "⏳ Attesa completamento CleanMgr (può richiedere alcuni minuti)..." + $cmDeadline = (Get-Date).AddHours(1) + while ((Get-Process -Name "cleanmgr" -ErrorAction SilentlyContinue) -and (Get-Date) -lt $cmDeadline) { + Start-Sleep -Seconds 10 + } + Add-CleanerLog -Type 'Info' -Text "✅ CleanMgr completato." } - Write-StyledMessage -Type 'Info' -Text "📅 Pulizia attività pianificate." - try { - $officeTasks = Get-ScheduledTask -ErrorAction SilentlyContinue | - Where-Object { $_.TaskName -like "*Office*" } - $tasksRemoved = 0 - foreach ($task in $officeTasks) { + } + @{ Name = "WinSxS Cleanup"; Type = "Command"; Command = "DISM.exe"; Args = @("/Online", "/Cleanup-Image", "/StartComponentCleanup", "/ResetBase") } + @{ Name = "Minimize DISM"; Type = "RegSet"; Key = "HKLM:\Software\Microsoft\Windows\CurrentVersion\SideBySide\Configuration"; ValueName = "DisableResetbase"; ValueData = 0; ValueType = "DWORD" } + @{ Name = "Error Reports"; Type = "File"; Paths = @( + "$env:ProgramData\Microsoft\Windows\WER", + "$env:ALLUSERSPROFILE\Microsoft\Windows\WER" + ); FilesOnly = $false + } + @{ Name = "Clear Event Logs"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "📜 Pulizia Event Logs (classici + moderni)." + $classicLogs = Get-EventLog -List -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Log + foreach ($logName in $classicLogs) { try { - Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction Stop - $tasksRemoved++ + Clear-EventLog -LogName $logName -ErrorAction Stop + Write-ToolkitLog -Level DEBUG -Message "Clear-EventLog: $logName" + } + catch { + Write-ToolkitLog -Level DEBUG -Message "Clear-EventLog [$logName]: $($_.Exception.Message)" } - catch {} } - if ($tasksRemoved -gt 0) { - Write-StyledMessage -Type 'Success' -Text "$tasksRemoved attività Office rimosse." + $wevtErr = $null + & wevtutil sl 'Microsoft-Windows-LiveId/Operational' /ca:'O:BAG:SYD:(A;;0x1;;;SY)(A;;0x5;;;BA)(A;;0x1;;;LA)' 2>&1 | Out-String -OutVariable wevtErr *>$null + if ($wevtErr) { Write-ToolkitLog -Level DEBUG -Message "wevtutil sl output: $wevtErr" } + Get-WinEvent -ListLog * -Force -ErrorAction SilentlyContinue | ForEach-Object { + $logName = $_.LogName + if ($_.LogType -in 'Analytical', 'Debug') { + Wevtutil.exe sl $logName /e:false *>$null + } + $clErr = $null + Wevtutil.exe cl $logName 2>&1 | Out-String -OutVariable clErr *>$null + if ($LASTEXITCODE -ne 0 -and $clErr) { Write-ToolkitLog -Level DEBUG -Message "Wevtutil cl [$logName]: $clErr" } } + Add-CleanerLog -Type 'Success' -Text "Event Log classici e moderni cancellati." } - catch {} - Write-StyledMessage -Type 'Info' -Text "🖥️ Rimozione collegamenti Office." - $officeShortcuts = @( - "Microsoft Word*.lnk", "Microsoft Excel*.lnk", "Microsoft PowerPoint*.lnk", - "Microsoft Outlook*.lnk", "Microsoft OneNote*.lnk", "Microsoft Access*.lnk", - "Office*.lnk", "Word*.lnk", "Excel*.lnk", "PowerPoint*.lnk", "Outlook*.lnk" - ) - $desktopPaths = @( - $AppConfig.Paths.Desktop, - "$env:PUBLIC\Desktop", - "$env:APPDATA\Microsoft\Windows\Start Menu\Programs", - "$env:ALLUSERSPROFILE\Microsoft\Windows\Start Menu\Programs" - ) - $shortcutsRemoved = 0 - foreach ($desktopPath in $desktopPaths) { - if (Test-Path $desktopPath) { - foreach ($shortcut in $officeShortcuts) { - $gciParams = @{ - Path = $desktopPath - Filter = $shortcut - Recurse = $true - ErrorAction = 'SilentlyContinue' + } + @{ Name = "Clear Windows Update cache"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🔄 Pulizia cache di Windows Update." + $services = @("wuauserv", "bits") + foreach ($s in $services) { + Invoke-ServiceAction -Rule @{ ServiceName = $s; Action = "Stop" } + } + $paths = @( + "C:\Windows\SoftwareDistribution\Download", + "C:\Windows\SoftwareDistribution\DataStore" + ) + foreach ($p in $paths) { + if (Test-Path $p) { + try { + Add-CleanerLog -Type 'Info' -Text "🗑️ Rimozione: $p" + Remove-Item -Path "$p\*" -Recurse -Force -ErrorAction SilentlyContinue } - $shortcutFiles = Get-ChildItem @gciParams - foreach ($file in $shortcutFiles) { - if (Invoke-SilentRemoval -Path $file.FullName) { - $shortcutsRemoved++ - } + catch { + Add-CleanerLog -Type 'Warning' -Text "Impossibile pulire completamente $p" } } } + foreach ($s in $services) { + Invoke-ServiceAction -Rule @{ ServiceName = $s; Action = "Start" } + } + Add-CleanerLog -Type 'Success' -Text "Windows Update cache cleared." } - if ($shortcutsRemoved -gt 0) { - Write-StyledMessage -Type 'Success' -Text "$shortcutsRemoved collegamenti Office rimossi." - } - Write-StyledMessage -Type 'Info' -Text "💽 Pulizia residui Office." - $additionalPaths = @( - "$env:LOCALAPPDATA\Microsoft\OneDrive", - "$env:APPDATA\Microsoft\OneDrive", - "$env:TEMP\Office*", - "$env:TEMP\MSO*" - ) - $residualsResult = Remove-ItemsSilently -Paths $additionalPaths -ItemType "residuo" - Write-StyledMessage -Type 'Success' -Text "✅ Rimozione diretta completata." - Write-StyledMessage -Type 'Info' -Text "📊 Riepilogo: $($folderResult.Count) cartelle, $($regResult.Count) chiavi registro, $shortcutsRemoved collegamenti, $tasksRemoved attività rimosse." - return $true } - catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante rimozione diretta Office: $($_.Exception.Message)." - return $false + @{ Name = "Windows App/Download Cache - User"; Type = "File"; Paths = @( + "%LOCALAPPDATA%\Microsoft\Windows\AppCache", + "%LOCALAPPDATA%\Microsoft\Windows\Caches" + ); PerUser = $true; FilesOnly = $true } - } - function Start-OfficeUninstallWithSaRA { - try { - if (-not (Test-Path $tempDir)) { - $null = New-Item -ItemType Directory -Path $tempDir -Force - } - $saraUrl = $AppConfig.URLs.SaRAInstaller - $saraZipPath = Join-Path $tempDir 'SaRA.zip' - if (-not (Invoke-DownloadFile $saraUrl $saraZipPath 'Microsoft SaRA')) { - return $false - } - Write-StyledMessage -Type 'Info' -Text "📦 Estrazione SaRA." - try { - Expand-Archive -Path $saraZipPath -DestinationPath $tempDir -Force - Write-StyledMessage -Type 'Success' -Text "Estrazione completata." - } - catch { - Write-StyledMessage -Type 'Error' -Text "Errore durante estrazione archivio SaRA: $($_.Exception.Message)." - return $false - } - $gciParamsExe = @{ - Path = $tempDir - Filter = "SaRACmd.exe" - Recurse = $true - ErrorAction = 'SilentlyContinue' - } - $saraExe = Get-ChildItem @gciParamsExe | Select-Object -First 1 - if (-not $saraExe) { - Write-StyledMessage -Type 'Error' -Text "SaRACmd.exe non trovato." - return $false - } - Write-StyledMessage -Type 'Info' -Text "🚀 Rimozione tramite SaRA (backup locale)." - Write-StyledMessage -Type 'Warning' -Text "⏰ Questa operazione può richiedere alcuni minuti." - $arguments = '-S OfficeScrubScenario -AcceptEula -OfficeVersion All' - try { - $processTimeoutSeconds = 86400 - $result = Invoke-WithSpinner -Activity "Rimozione Office tramite SaRA" -Process -Action { - $procParams = @{ - FilePath = $saraExe.FullName - ArgumentList = $arguments - Verb = 'RunAs' - PassThru = $true - ErrorAction = 'Stop' + @{ Name = "System Restore Points"; Type = "ScriptBlock"; ScriptBlock = { + try { + Add-CleanerLog -Type 'Info' -Text "💾 Pulizia punti di ripristino sistema." + Add-CleanerLog -Type 'Info' -Text "🗑️ Analisi e pulizia shadow copies (mantieni ultima)." + try { + $shadows = Get-CimInstance -ClassName Win32_ShadowCopy -ErrorAction Stop | Sort-Object InstallDate -Descending + if ($shadows.Count -gt 1) { + $toDelete = $shadows | Select-Object -Skip 1 + $count = $toDelete.Count + Add-CleanerLog -Type 'Info' -Text "Rilevate $($shadows.Count) shadow copies. Rimozione di $count vecchie." + foreach ($shadow in $toDelete) { + Remove-CimInstance -InputObject $shadow -ErrorAction SilentlyContinue + } + Add-CleanerLog -Type 'Success' -Text "Vecchie shadow copies rimosse. Ultima copia preservata." + } + elseif ($shadows.Count -eq 1) { + Add-CleanerLog -Type 'Info' -Text "Trovata una sola shadow copy. Nessuna rimozione necessaria." + } + else { + Add-CleanerLog -Type 'Info' -Text "Nessuna shadow copy rilevata." + } } - Start-Process @procParams - } -TimeoutSeconds $processTimeoutSeconds -UpdateInterval 1000 - if ($result.ExitCode -eq 0) { - Write-StyledMessage -Type 'Success' -Text "✅ SaRA completato con successo." - return $true + catch { + Add-CleanerLog -Type 'Warning' -Text "Errore gestione shadow copies: $_" + } + Add-CleanerLog -Type 'Info' -Text "💡 Protezione sistema mantenuta attiva per sicurezza" + Add-CleanerLog -Type 'Success' -Text "Pulizia punti di ripristino completata" } - else { - Write-StyledMessage -Type 'Warning' -Text "SaRA terminato con codice: $($result.ExitCode)." - Write-StyledMessage -Type 'Info' -Text "💡 Tentativo metodo alternativo." - return Remove-OfficeDirectly + catch { + Add-CleanerLog -Type 'Warning' -Text "Errore durante la pulizia punti di ripristino: $($_.Exception.Message)" } } - catch { - Write-StyledMessage -Type 'Warning' -Text "Errore durante esecuzione SaRA: $($_.Exception.Message)." - Write-StyledMessage -Type 'Info' -Text "💡 Passaggio a metodo alternativo." - return Remove-OfficeDirectly - } - } - catch { - Write-StyledMessage -Type 'Warning' -Text "Errore durante processo SaRA: $($_.Exception.Message)." - return $false - } - finally { - Invoke-SilentRemoval -Path $tempDir -Recurse - } - } - function Start-OfficeUninstall { - Write-StyledMessage -Type 'Warning' -Text "🗑️ Avvio rimozione completa Microsoft Office." - Stop-OfficeProcesses - Write-StyledMessage -Type 'Info' -Text "🔍 Rilevamento versione Windows." - $windowsVersion = Get-WindowsVersion - Write-StyledMessage -Type 'Info' -Text "🎯 Versione rilevata: $windowsVersion." - $success = $false - switch ($windowsVersion) { - 'Windows11_23H2_Plus' { - Write-StyledMessage -Type 'Info' -Text "🚀 Utilizzo metodo SaRA per Windows 11 23H2+." - $success = Start-OfficeUninstallWithSaRA - } - default { - Write-StyledMessage -Type 'Info' -Text "⚡ Utilizzo rimozione diretta per Windows 11 22H2 o precedenti." - $success = Remove-OfficeDirectly - } - } - if ($success) { - Write-StyledMessage -Type 'Success' -Text "🎉 Rimozione Office completata!" - return $true - } - else { - Write-StyledMessage -Type 'Error' -Text "Rimozione non completata." - Write-StyledMessage -Type 'Info' -Text "💡 Puoi provare un metodo alternativo o rimozione manuale." - return $false } - } - Write-StyledMessage -Type 'Progress' -Text "⏳ Inizializzazione sistema." - Start-Sleep 2 - Write-StyledMessage -Type 'Success' -Text "✅ Sistema pronto." - $needsReboot = $false - $lastOperation = '' - try { - do { - Write-StyledMessage -Type 'Info' -Text "🎯 Seleziona un'opzione:" - Write-StyledMessage -Type 'Info' -Text " [1] 🏢 Installazione Office (Basic Version)" - Write-StyledMessage -Type 'Info' -Text " [2] 🔧 Ripara Office" - Write-StyledMessage -Type 'Info' -Text " [3] 🗑️ Rimozione completa Office" - Write-StyledMessage -Type 'Info' -Text " [0] ❌ Esci" - $choice = Read-Host 'Scelta [0-3]' - $success = $false - $operation = '' - switch ($choice) { - '1' { - $operation = 'Installazione' - $success = Start-OfficeInstallation - } - '2' { - $operation = 'Riparazione' - $success = Start-OfficeRepair - } - '3' { - $operation = 'Rimozione' - $success = Start-OfficeUninstall - } - '0' { - Write-StyledMessage -Type 'Info' -Text "👋 Uscita dal toolkit." - break - } - default { - Write-StyledMessage -Type 'Warning' -Text "Opzione non valida. Seleziona 0-3." - continue + @{ Name = "Cleanup - Windows Prefetch Cache"; Type = "File"; Paths = @("C:\WINDOWS\Prefetch"); FilesOnly = $false } + @{ Name = "Cleanup - Explorer Thumbnail/Icon Cache"; Type = "File"; Paths = @("%LOCALAPPDATA%\Microsoft\Windows\Explorer"); PerUser = $true; FilesOnly = $true; TakeOwnership = $true } + @{ Name = "WinInet Cache - User"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🌐 Pulizia cache WinInet/WebCache." + $cacheTaskDisabled = $false + try { + $ct = Get-ScheduledTask -TaskPath '\Microsoft\Windows\Wininet\' -TaskName 'CacheTask' -ErrorAction SilentlyContinue + if ($ct -and $ct.State -ne 'Disabled') { + Stop-ScheduledTask -TaskPath '\Microsoft\Windows\Wininet\' -TaskName 'CacheTask' -ErrorAction SilentlyContinue + Disable-ScheduledTask -TaskPath '\Microsoft\Windows\Wininet\' -TaskName 'CacheTask' -ErrorAction SilentlyContinue *>$null + $cacheTaskDisabled = $true + Start-Sleep -Seconds 2 + } } - } - if ($choice -in @('1', '2', '3')) { - if ($success) { - if ($choice -ne '1') { - Write-StyledMessage -Type 'Success' -Text "🎉 $operation completata!" - $needsReboot = $true - $lastOperation = $operation - Write-StyledMessage -Type 'Info' -Text "💡 Il sistema verrà riavviato automaticamente alla fine del processo." + catch { Write-ToolkitLog -Level DEBUG -Message "CacheTask disable error: $_" } + $users = Get-LocalUserProfiles + foreach ($u in $users) { + $paths = @( + "$($u.FullName)\AppData\Local\Microsoft\Windows\INetCache\IE", + "$($u.FullName)\AppData\Local\Microsoft\Windows\WebCache", + "$($u.FullName)\AppData\Local\Microsoft\Feeds Cache", + "$($u.FullName)\AppData\Local\Microsoft\InternetExplorer\DOMStore", + "$($u.FullName)\AppData\Local\Microsoft\Internet Explorer" + ) + foreach ($p in $paths) { + if (-not (Test-Path $p)) { continue } + Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue + if (Test-Path $p) { + Get-ChildItem -Path $p -Recurse -File -Force -ErrorAction SilentlyContinue | + ForEach-Object { Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue } + Get-ChildItem -Path $p -Recurse -Directory -Force -ErrorAction SilentlyContinue | + Sort-Object { $_.FullName.Length } -Descending | + ForEach-Object { Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue } + } } } - else { - Write-StyledMessage -Type 'Error' -Text "$operation non riuscita." - Write-StyledMessage -Type 'Info' -Text "💡 Controlla i log per dettagli o contatta il supporto." + if ($cacheTaskDisabled) { + try { + Enable-ScheduledTask -TaskPath '\Microsoft\Windows\Wininet\' -TaskName 'CacheTask' -ErrorAction SilentlyContinue *>$null + } + catch { Write-ToolkitLog -Level DEBUG -Message "CacheTask enable error: $_" } } - Write-StyledMessage -Type 'Info' -Text ('─' * 50) + Add-CleanerLog -Type 'Success' -Text "✅ Cache WinInet/WebCache pulita." } - } while ($choice -ne '0') - } - catch { - Write-StyledMessage -Type 'Error' -Text "Errore critico durante esecuzione OfficeToolkit: $($_.Exception.Message)." - Write-ToolkitLog -Level ERROR -Message "Errore critico in OfficeToolkit" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace } - } - finally { - Write-StyledMessage -Type 'Success' -Text "🧹 Pulizia finale." - Invoke-SilentRemoval -Path $tempDir -Recurse - Write-StyledMessage -Type 'Success' -Text "🎯 Office Toolkit terminato." - Write-ToolkitLog -Level INFO -Message "OfficeToolkit sessione terminata." - } - if ($needsReboot) { - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio individuale soppresso. Verrà gestito un riavvio finale." + @{ Name = "Temporary Internet Files"; Type = "File"; Paths = @( + "%USERPROFILE%\Local Settings\Temporary Internet Files" + ); PerUser = $true; FilesOnly = $false } - else { - if (Start-InterruptibleCountdown -Seconds $CountdownSeconds -Message "$lastOperation completata") { - Restart-Computer -Force - } + @{ Name = "Cache/History Cleanup"; Type = "Command"; Command = "RunDll32.exe"; Args = @("InetCpl.cpl", "ClearMyTracksByProcess", "8") } + @{ Name = "Form Data Cleanup"; Type = "Command"; Command = "RunDll32.exe"; Args = @("InetCpl.cpl", "ClearMyTracksByProcess", "2") } + @{ Name = "Internet Cookies Cleanup"; Type = "File"; Paths = @( + "%APPDATA%\Microsoft\Windows\Cookies", + "%LOCALAPPDATA%\Microsoft\Windows\INetCookies" + ); PerUser = $true; FilesOnly = $false } - } -} -function WinCleaner { - [CmdletBinding()] - param( - [Parameter(Mandatory = $false)] - [ValidateRange(0, 300)] - [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] - [switch]$SuppressIndividualReboot - ) - $global:WinCleanerLog = @() - function Add-CleanerLog { - param( - [Parameter(Mandatory = $true, Position = 0)] - [ValidateSet('Success', 'Info', 'Warning', 'Error', 'Question')] - [string]$Type, - [Parameter(Mandatory = $true, Position = 1)] - [string]$Text - ) - Clear-ProgressLine - $logEntry = @{ - Timestamp = Get-Date -Format "HH:mm:ss" - Type = $Type - Text = $Text - } - $global:WinCleanerLog += $logEntry - Write-StyledMessage -Type $Type -Text $Text - } - Start-ToolkitLog -ToolName "WinCleaner" - Show-Header -SubTitle "Cleaner Toolkit" - $Host.UI.RawUI.WindowTitle = "Cleaner Toolkit By MagnetarMan" - $timeout = 86400 - $ProgressPreference = 'Continue' - $VitalExclusions = @( - "$env:LOCALAPPDATA\WinToolkit" - ) - function Test-VitalExclusion { - param([string]$Path) - if ([string]::IsNullOrWhiteSpace($Path)) { return $false } - $fullPath = $Path -replace '"', '' - try { - if (-not [System.IO.Path]::IsPathRooted($fullPath)) { - $fullPath = Join-Path (Get-Location) $fullPath - } - foreach ($excluded in $VitalExclusions) { - if ($fullPath -like "$excluded*" -or $fullPath -eq $excluded) { - Add-CleanerLog -Type 'Info' -Text "🛡️ PROTEZIONE VITALE ATTIVATA: $fullPath" - return $true - } - } - } - catch { return $false } - return $false - } - function Start-ProcessWithTimeout { - param( - [Parameter(Mandatory = $true)] - [string]$FilePath, - [Parameter(Mandatory = $false)] - [string[]]$ArgumentList = @(), - [Parameter(Mandatory = $false)] - [int]$TimeoutSeconds = 86400, - [Parameter(Mandatory = $false)] - [string]$Activity = "Processo in esecuzione", - [Parameter(Mandatory = $false)] - [switch]$Hidden - ) - $processParams = @{ - FilePath = $FilePath - ArgumentList = $ArgumentList - PassThru = $true - ErrorAction = 'Stop' - } - if ($Hidden) { $processParams.WindowStyle = 'Hidden' } else { $processParams.NoNewWindow = $true } - $proc = Start-Process @processParams - $result = Invoke-WithSpinner -Activity $Activity -Process -Action { $proc } -TimeoutSeconds $TimeoutSeconds -UpdateInterval 500 - return $result - } - function Invoke-CommandAction { - param($Rule) - Clear-ProgressLine - Write-StyledMessage -Type 'Info' -Text "🚀 Esecuzione comando: $($Rule.Name)." - try { - $timeoutCommands = @("DISM.exe", "cleanmgr.exe") - if ($Rule.Command -in $timeoutCommands) { - $result = Start-ProcessWithTimeout -FilePath $Rule.Command -ArgumentList $Rule.Args -TimeoutSeconds $timeout -Activity $Rule.Name -Hidden - if ($result.TimedOut) { Write-StyledMessage -Type 'Warning' -Text "Comando timeout dopo 24 ore."; return $true } - if ($result.ExitCode -eq -2146498554 -or $result.ExitCode -eq 0x800F0818) { - Add-CleanerLog -Type 'Warning' -Text "ATTENZIONE! - Stai effettuando la pulizia con Windows Update in corso. Aggiorna il sistema e riprova per eseguire la pulizia completa" - return $false - } - Add-CleanerLog -Type ($result.ExitCode -eq 0 ? 'Info' : 'Warning') -Text ($result.ExitCode -eq 0 ? "Comando completato." : "Comando completato con codice $($result.ExitCode)") - return $true - } - else { - $procParams = @{ - FilePath = $Rule.Command - ArgumentList = $Rule.Args - PassThru = $true - WindowStyle = 'Hidden' - Wait = $true - ErrorAction = 'SilentlyContinue' - } - $proc = Start-Process @procParams - if ($proc.ExitCode -ne 0) { - Add-CleanerLog -Type 'Warning' -Text "Comando completato con codice $($proc.ExitCode)" - } - return $true - } - } - catch { - Add-CleanerLog -Type 'Error' -Text "Errore comando: $_" - return $false - } - } - function Invoke-ServiceAction { - param($Rule) - $svcName = $Rule.ServiceName - $action = $Rule.Action - try { - $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue - if (-not $svc) { return $true } - if ($action -eq 'Stop' -and $svc.Status -eq 'Running') { - Add-CleanerLog -Type 'Info' -Text "⏸️ Arresto servizio $svcName." - Stop-Service -Name $svcName -Force -ErrorAction Stop | Out-Null - } - elseif ($action -eq 'Start' -and $svc.Status -ne 'Running') { - Add-CleanerLog -Type 'Info' -Text "▶️ Avvio servizio $svcName." - Start-Service -Name $svcName -ErrorAction Stop | Out-Null - } - return $true - } - catch { - Add-CleanerLog -Type 'Warning' -Text "Errore servizio $svcName : $_" - return $false - } - } - function Remove-FileItem { - param($Rule) - $paths = $Rule.Paths - $isPerUser = $Rule.PerUser - $filesOnly = $Rule.FilesOnly - $takeOwn = $Rule.TakeOwnership - $targetPaths = @() - if ($isPerUser) { - $users = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch '^(Public|Default|All Users)$' } - foreach ($user in $users) { - foreach ($p in $paths) { - $targetPaths += $p -replace '%USERPROFILE%', $user.FullName ` - -replace '%APPDATA%', "$($user.FullName)\AppData\Roaming" ` - -replace '%LOCALAPPDATA%', "$($user.FullName)\AppData\Local" ` - -replace '%TEMP%', "$($user.FullName)\AppData\Local\Temp" + @{ Name = "Cookies Cleanup"; Type = "Command"; Command = "RunDll32.exe"; Args = @("InetCpl.cpl", "ClearMyTracksByProcess", "1") } + @{ Name = "Chromium Browsers Cache (Chrome, Edge, Brave, Vivaldi)"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🌐 Pulizia Cache Browser Chromium." + $browsers = @( + @{ Name = "Google Chrome"; Path = "Google\Chrome\User Data" }, + @{ Name = "Microsoft Edge"; Path = "Microsoft\Edge\User Data" }, + @{ Name = "Brave Browser"; Path = "BraveSoftware\Brave-Browser\User Data" }, + @{ Name = "Vivaldi"; Path = "Vivaldi\User Data" } + ) + $users = Get-LocalUserProfiles + foreach ($u in $users) { + foreach ($b in $browsers) { + $userDataPath = Join-Path "$($u.FullName)\AppData\Local" $b.Path + if (Test-Path $userDataPath) { + $patterns = @( + "$userDataPath\*\Cache", + "$userDataPath\*\Code Cache", + "$userDataPath\*\GPUCache", + "$userDataPath\*\ShaderCache", + "$userDataPath\CrashReports" + ) + foreach ($p in $patterns) { + Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue + } + } + } } } } - else { - foreach ($p in $paths) { $targetPaths += [Environment]::ExpandEnvironmentVariables($p) } - } - $count = 0 - foreach ($path in $targetPaths) { - if (Test-VitalExclusion $path) { continue } - if (-not (Test-Path $path)) { continue } - try { - if ($takeOwn) { - Add-CleanerLog -Type 'Info' -Text "🔑 Assunzione proprietà per $path." - $null = & cmd /c "takeown /F `"$path`" /R /A >nul 2>&1" - $adminSID = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544') - $adminAccount = $adminSID.Translate([System.Security.Principal.NTAccount]).Value - $null = & cmd /c "icacls `"$path`" /T /grant `"${adminAccount}:F`" >nul 2>&1" + @{ Name = "Google Chrome AI OptGuide Model"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🤖 Pulizia e disattivazione AI Chrome (OptGuide)." + $users = Get-LocalUserProfiles + foreach ($u in $users) { + $optGuidePath = Join-Path "$($u.FullName)\AppData\Local" "Google\Chrome\User Data\OptGuideOnDeviceModel" + if (Test-Path $optGuidePath) { + try { + Add-CleanerLog -Type 'Info' -Text "🗑️ Rimozione cartella OptGuide: $optGuidePath" + Remove-Item -Path $optGuidePath -Recurse -Force -ErrorAction Stop + } + catch { + Add-CleanerLog -Type 'Warning' -Text "Errore rimozione $optGuidePath : $_" + } + } + try { + if (-not (Test-Path $optGuidePath)) { + New-Item -Path $optGuidePath -ItemType Directory -Force -ErrorAction Stop *>$null + } + $acl = Get-Acl -Path $optGuidePath -ErrorAction Stop + $acl.SetAccessRuleProtection($true, $false) + $denyRule = New-Object System.Security.AccessControl.FileSystemAccessRule( + "Everyone", "Write", "ContainerInherit,ObjectInherit", "None", "Deny" + ) + $acl.AddAccessRule($denyRule) + Set-Acl -Path $optGuidePath -AclObject $acl -ErrorAction Stop + Add-CleanerLog -Type 'Success' -Text "🔒 Cartella OptGuide impostata in sola lettura: $optGuidePath" + } + catch { + Add-CleanerLog -Type 'Warning' -Text "Errore impostazione read-only per $optGuidePath : $_" + } } - if ($filesOnly) { - $files = Get-ChildItem -Path $path -File -Force -ErrorAction SilentlyContinue - foreach ($file in $files) { - Remove-Item -Path $file.FullName -Force -ErrorAction Stop + $chromePolicyKey = "HKLM:\SOFTWARE\Policies\Google\Chrome" + try { + if (-not (Test-Path $chromePolicyKey)) { + New-Item -Path $chromePolicyKey -Force -ErrorAction Stop *>$null + } + $aiPolicies = @{ + "GenAILocalFoundationalModelSettings" = 1 + "AIModeSettings" = 2 + "GeminiSettings" = 1 + "HelpMeWriteSettings" = 2 + "DevToolsGenAiSettings" = 2 + } + foreach ($policy in $aiPolicies.GetEnumerator()) { + Set-ItemProperty -Path $chromePolicyKey -Name $policy.Key -Value $policy.Value -Type DWORD -Force -ErrorAction Stop + Add-CleanerLog -Type 'Success' -Text "⚙️ Policy Chrome impostata: $($policy.Key) = $($policy.Value)" } } - else { - Remove-Item -Path $path -Recurse -Force -ErrorAction Stop + catch { + Add-CleanerLog -Type 'Warning' -Text "Errore impostazione policy Chrome AI: $_" } - $count++ - } - catch { - Add-CleanerLog -Type 'Warning' -Text "Errore rimozione $path : $_" } } - if ($count -gt 0) { Write-StyledMessage -Type 'Success' -Text "🗑️ Puliti $count elementi in $($Rule.Name)." } - return $true - } - function Remove-RegistryItem { - param($Rule) - $keys = $Rule.Keys - $recursive = $Rule.Recursive - $valuesOnly = $Rule.ValuesOnly - foreach ($rawKey in $keys) { - $key = $rawKey -replace '^(HKCU|HKLM):\\*', '$1:\' - if (-not (Test-Path $key)) { continue } - try { - if ($valuesOnly) { - $item = Get-Item $key -ErrorAction Stop - $item.GetValueNames() | ForEach-Object { - if ($_ -ne '(default)') { Remove-ItemProperty -LiteralPath $key -Name $_ -Force -ErrorAction SilentlyContinue | Out-Null } + @{ Name = "Firefox Browser Cache"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🦊 Pulizia Firefox (Cache & Crashes)." + $users = Get-LocalUserProfiles + foreach ($u in $users) { + $cleanPaths = @( + "$($u.FullName)\AppData\Local\Mozilla\Firefox\Profiles", + "$($u.FullName)\AppData\Local\Mozilla\Firefox\Crash Reports" + ) + foreach ($p in $cleanPaths) { + if (Test-Path $p) { Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue } } - if ($recursive) { - Get-ChildItem $key -Recurse -ErrorAction SilentlyContinue | ForEach-Object { - $currentKeyPath = $_.PSPath - $_.GetValueNames() | ForEach-Object { Remove-ItemProperty -LiteralPath $currentKeyPath -Name $_ -Force -ErrorAction SilentlyContinue | Out-Null } - } + $msStoreProfiles = Get-ChildItem ` + "$($u.FullName)\AppData\Local\Packages" ` + -Directory -Filter "Mozilla.Firefox_*" ` + -ErrorAction SilentlyContinue + foreach ($pkg in $msStoreProfiles) { + $msCache = "$($pkg.FullName)\LocalCache\Roaming\Mozilla\Firefox\Profiles" + if (Test-Path $msCache) { Remove-Item -Path $msCache -Recurse -Force -ErrorAction SilentlyContinue } } - Add-CleanerLog -Type 'Success' -Text "⚙️ Puliti valori in $key" - } - else { - Remove-Item -Path $key -Recurse:$recursive -Force -ErrorAction Stop - Add-CleanerLog -Type 'Success' -Text "🗑️ Rimossa chiave $key" } } - catch { - Add-CleanerLog -Type 'Warning' -Text "Errore registro $key : $_" - } } - return $true - } - function Set-RegistryItem { - param($Rule) - $key = $Rule.Key -replace '^(HKCU|HKLM):', '$1:\' - try { - if (-not (Test-Path $key)) { New-Item -Path $key -Force -ErrorAction SilentlyContinue | Out-Null } - Set-ItemProperty -Path $key -Name $Rule.ValueName -Value $Rule.ValueData -Type $Rule.ValueType -Force -ErrorAction SilentlyContinue | Out-Null - Add-CleanerLog -Type 'Success' -Text "⚙️ Impostato $key\$($Rule.ValueName)" - return $true + @{ Name = "Edge Legacy (HTML) Cache"; Type = "File"; Paths = @( + "%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\*\MicrosoftEdge\Cache", + "%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\#!001\MicrosoftEdge\Cache" + ); PerUser = $true; FilesOnly = $false } - catch { return $false } - } - function Invoke-WinCleanerRule { - param($Rule) - Clear-ProgressLine - switch ($Rule.Type) { - 'File' { return Remove-FileItem -Rule $Rule } - 'Registry' { return Remove-RegistryItem -Rule $Rule } - 'RegSet' { return Set-RegistryItem -Rule $Rule } - 'Service' { return Invoke-ServiceAction -Rule $Rule } - 'Command' { return Invoke-CommandAction -Rule $Rule } - 'ScriptBlock' { - if ($Rule.ScriptBlock) { - & $Rule.ScriptBlock - return $true - } - } - 'Custom' { - if ($Rule.ScriptBlock) { - & $Rule.ScriptBlock - return $true - } - } - } - return $true - } - $Rules = @( - @{ Name = "CleanMgr Config"; Type = "Custom"; ScriptBlock = { - Add-CleanerLog -Type 'Info' -Text "🧹 Configurazione CleanMgr." - $reg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches" - $opts = @( - "Active Setup Temp Folders", - "BranchCache", - "D3D Shader Cache", - "Delivery Optimization Files", - "Downloaded Program Files", - "Internet Cache Files", - "Memory Dump Files", - "Recycle Bin", - "Temporary Files", - "Thumbnail Cache", - "Windows Error Reporting Files", - "Setup Log Files", - "System error memory dump files", - "System error minidump files", - "Temporary Setup Files", - "Windows Upgrade Log Files" - ) - foreach ($o in $opts) { - $p = Join-Path $reg $o - if (Test-Path $p) { Set-ItemProperty -Path $p -Name "StateFlags0065" -Value 2 -Type DWORD -Force -ErrorAction SilentlyContinue } - } - $cleanMgrExecutionRule = @{ - Name = "Esecuzione CleanMgr con /sagerun:65"; - Type = "Command"; - Command = "cleanmgr.exe"; - Args = @("/sagerun:65"); - } - Invoke-CommandAction -Rule $cleanMgrExecutionRule - } - } - @{ Name = "WinSxS Cleanup"; Type = "Command"; Command = "DISM.exe"; Args = @("/Online", "/Cleanup-Image", "/StartComponentCleanup", "/ResetBase") } - @{ Name = "Minimize DISM"; Type = "RegSet"; Key = "HKLM:\Software\Microsoft\Windows\CurrentVersion\SideBySide\Configuration"; ValueName = "DisableResetbase"; ValueData = 0; ValueType = "DWORD" } - @{ Name = "Error Reports"; Type = "File"; Paths = @( - "$env:ProgramData\Microsoft\Windows\WER", - "$env:ALLUSERSPROFILE\Microsoft\Windows\WER" - ); FilesOnly = $false - } - @{ Name = "Clear Event Logs"; Type = "Custom"; ScriptBlock = { - Add-CleanerLog -Type 'Info' -Text "📜 Pulizia Event Logs." - $wevtErr = $null - & wevtutil sl 'Microsoft-Windows-LiveId/Operational' /ca:'O:BAG:SYD:(A;;0x1;;;SY)(A;;0x5;;;BA)(A;;0x1;;;LA)' 2>&1 | Out-String -OutVariable wevtErr | Out-Null - if ($wevtErr) { Write-ToolkitLog -Level DEBUG -Message "wevtutil sl output: $wevtErr" } - Get-WinEvent -ListLog * -Force -ErrorAction SilentlyContinue | ForEach-Object { - $logName = $_.LogName - $clErr = $null - Wevtutil.exe cl $logName 2>&1 | Out-String -OutVariable clErr | Out-Null - if ($LASTEXITCODE -ne 0 -and $clErr) { Write-ToolkitLog -Level DEBUG -Message "Wevtutil cl [$logName]: $clErr" } - } - } - } - @{ Name = "Clear Windows Update cache"; Type = "Custom"; ScriptBlock = { - Add-CleanerLog -Type 'Info' -Text "🔄 Pulizia cache di Windows Update." - $services = @("wuauserv", "bits") - foreach ($s in $services) { - Invoke-ServiceAction -Rule @{ ServiceName = $s; Action = "Stop" } - } - $paths = @( - "C:\Windows\SoftwareDistribution\Download", - "C:\Windows\SoftwareDistribution\DataStore" - ) - foreach ($p in $paths) { - if (Test-Path $p) { - try { - Add-CleanerLog -Type 'Info' -Text "🗑️ Rimozione: $p" - Remove-Item -Path "$p\*" -Recurse -Force -ErrorAction SilentlyContinue - } - catch { - Add-CleanerLog -Type 'Warning' -Text "Impossibile pulire completamente $p" - } - } - } - foreach ($s in $services) { - Invoke-ServiceAction -Rule @{ ServiceName = $s; Action = "Start" } - } - Add-CleanerLog -Type 'Success' -Text "Windows Update cache cleared." - } - } - @{ Name = "Windows App/Download Cache - User"; Type = "File"; Paths = @( - "%LOCALAPPDATA%\Microsoft\Windows\AppCache", - "%LOCALAPPDATA%\Microsoft\Windows\Caches" - ); PerUser = $true; FilesOnly = $true - } - @{ Name = "System Restore Points"; Type = "ScriptBlock"; ScriptBlock = { - try { - Add-CleanerLog -Type 'Info' -Text "💾 Pulizia punti di ripristino sistema." - Add-CleanerLog -Type 'Info' -Text "🗑️ Analisi e pulizia shadow copies (mantieni ultima)." - try { - $shadows = Get-CimInstance -ClassName Win32_ShadowCopy -ErrorAction Stop | Sort-Object InstallDate -Descending - if ($shadows.Count -gt 1) { - $toDelete = $shadows | Select-Object -Skip 1 - $count = $toDelete.Count - Add-CleanerLog -Type 'Info' -Text "Rilevate $($shadows.Count) shadow copies. Rimozione di $count vecchie." - foreach ($shadow in $toDelete) { - Remove-CimInstance -InputObject $shadow -ErrorAction SilentlyContinue - } - Add-CleanerLog -Type 'Success' -Text "Vecchie shadow copies rimosse. Ultima copia preservata." - } - elseif ($shadows.Count -eq 1) { - Add-CleanerLog -Type 'Info' -Text "Trovata una sola shadow copy. Nessuna rimozione necessaria." - } - else { - Add-CleanerLog -Type 'Info' -Text "Nessuna shadow copy rilevata." - } - } - catch { - Add-CleanerLog -Type 'Warning' -Text "Errore gestione shadow copies: $_" - } - Add-CleanerLog -Type 'Info' -Text "💡 Protezione sistema mantenuta attiva per sicurezza" - Add-CleanerLog -Type 'Success' -Text "Pulizia punti di ripristino completata" - } - catch { - Add-CleanerLog -Type 'Warning' -Text "Errore durante la pulizia punti di ripristino: $($_.Exception.Message)" - } - } - } - @{ Name = "Cleanup - Windows Prefetch Cache"; Type = "File"; Paths = @("C:\WINDOWS\Prefetch"); FilesOnly = $false } - @{ Name = "Cleanup - Explorer Thumbnail/Icon Cache"; Type = "File"; Paths = @("%LOCALAPPDATA%\Microsoft\Windows\Explorer"); PerUser = $true; FilesOnly = $true; TakeOwnership = $true } - @{ Name = "WinInet Cache - User"; Type = "File"; Paths = @( - "%LOCALAPPDATA%\Microsoft\Windows\INetCache\IE", - "%LOCALAPPDATA%\Microsoft\Windows\WebCache", - "%LOCALAPPDATA%\Microsoft\Feeds Cache", - "%LOCALAPPDATA%\Microsoft\InternetExplorer\DOMStore", - "%LOCALAPPDATA%\Microsoft\Internet Explorer" - ); PerUser = $true; FilesOnly = $false - } - @{ Name = "Temporary Internet Files"; Type = "File"; Paths = @( - "%USERPROFILE%\Local Settings\Temporary Internet Files" - ); PerUser = $true; FilesOnly = $false - } - @{ Name = "Cache/History Cleanup"; Type = "Command"; Command = "RunDll32.exe"; Args = @("InetCpl.cpl", "ClearMyTracksByProcess", "8") } - @{ Name = "Form Data Cleanup"; Type = "Command"; Command = "RunDll32.exe"; Args = @("InetCpl.cpl", "ClearMyTracksByProcess", "2") } - @{ Name = "Internet Cookies Cleanup"; Type = "File"; Paths = @( - "%APPDATA%\Microsoft\Windows\Cookies", - "%LOCALAPPDATA%\Microsoft\Windows\INetCookies" - ); PerUser = $true; FilesOnly = $false - } - @{ Name = "Cookies Cleanup"; Type = "Command"; Command = "RunDll32.exe"; Args = @("InetCpl.cpl", "ClearMyTracksByProcess", "1") } - @{ Name = "Chromium Browsers Cache (Chrome, Edge, Brave, Vivaldi)"; Type = "Custom"; ScriptBlock = { - Add-CleanerLog -Type 'Info' -Text "🌐 Pulizia Cache Browser Chromium." - $browsers = @( - @{ Name = "Google Chrome"; Path = "Google\Chrome\User Data" }, - @{ Name = "Microsoft Edge"; Path = "Microsoft\Edge\User Data" }, - @{ Name = "Brave Browser"; Path = "BraveSoftware\Brave-Browser\User Data" }, - @{ Name = "Vivaldi"; Path = "Vivaldi\User Data" } - ) - $users = Get-ChildItem "C:\Users" -Directory | Where-Object { $_.Name -notmatch '^(Public|Default|All Users)$' } - foreach ($u in $users) { - foreach ($b in $browsers) { - $userDataPath = Join-Path "$($u.FullName)\AppData\Local" $b.Path - if (Test-Path $userDataPath) { - $patterns = @( - "$userDataPath\*\Cache", - "$userDataPath\*\Code Cache", - "$userDataPath\*\GPUCache", - "$userDataPath\*\ShaderCache", - "$userDataPath\CrashReports" - ) - foreach ($p in $patterns) { - Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue - } - } - } - } - } - } - @{ Name = "Firefox Browser Cache"; Type = "Custom"; ScriptBlock = { - Add-CleanerLog -Type 'Info' -Text "🦊 Pulizia Firefox (Cache & Crashes)." - $users = Get-ChildItem "C:\Users" -Directory | Where-Object { $_.Name -notmatch '^(Public|Default|All Users)$' } - foreach ($u in $users) { - $cleanPaths = @( - "$($u.FullName)\AppData\Local\Mozilla\Firefox\Profiles", - "$($u.FullName)\AppData\Local\Mozilla\Firefox\Crash Reports" - ) - foreach ($p in $cleanPaths) { - if (Test-Path $p) { Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue } - } - $msStoreProfiles = Get-ChildItem ` - "$($u.FullName)\AppData\Local\Packages" ` - -Directory -Filter "Mozilla.Firefox_*" ` - -ErrorAction SilentlyContinue - foreach ($pkg in $msStoreProfiles) { - $msCache = "$($pkg.FullName)\LocalCache\Roaming\Mozilla\Firefox\Profiles" - if (Test-Path $msCache) { Remove-Item -Path $msCache -Recurse -Force -ErrorAction SilentlyContinue } - } - } - } - } - @{ Name = "Edge Legacy (HTML) Cache"; Type = "File"; Paths = @( - "%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\*\MicrosoftEdge\Cache", - "%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\#!001\MicrosoftEdge\Cache" - ); PerUser = $true; FilesOnly = $false - } - @{ Name = "Opera & Java Cache"; Type = "File"; Paths = @( - "%USERPROFILE%\Local Settings\Application Data\Opera\Opera", - "%LOCALAPPDATA%\Opera\Opera", - "%APPDATA%\Opera\Opera", - "%APPDATA%\Sun\Java\Deployment\cache" - ); PerUser = $true; FilesOnly = $false + @{ Name = "Opera & Java Cache"; Type = "File"; Paths = @( + "%USERPROFILE%\Local Settings\Application Data\Opera\Opera", + "%LOCALAPPDATA%\Opera\Opera", + "%APPDATA%\Opera\Opera", + "%APPDATA%\Sun\Java\Deployment\cache" + ); PerUser = $true; FilesOnly = $false } @{ Name = "DNS Flush"; Type = "Command"; Command = "ipconfig"; Args = @("/flushdns") } @{ Name = "System Temp Files"; Type = "File"; Paths = @("C:\WINDOWS\Temp"); FilesOnly = $false } @{ Name = "User Temp Files"; Type = "File"; Paths = @( - "%TEMP%", "%USERPROFILE%\AppData\Local\Temp", "%USERPROFILE%\AppData\LocalLow\Temp" ); PerUser = $true; FilesOnly = $false @@ -3526,7 +3293,7 @@ function WinCleaner { try { Add-CleanerLog -Type 'Info' -Text "🖨️ Pulizia coda di stampa (Spooler)." Add-CleanerLog -Type 'Info' -Text "⏸️ Arresto servizio Spooler." - Stop-Service -Name Spooler -Force -ErrorAction Stop | Out-Null + Stop-Service -Name Spooler -Force -ErrorAction Stop *>$null Add-CleanerLog -Type 'Info' -Text "Servizio Spooler arrestato." Start-Sleep -Seconds 2 $printersPath = 'C:\WINDOWS\System32\spool\PRINTERS' @@ -3536,7 +3303,7 @@ function WinCleaner { Add-CleanerLog -Type 'Info' -Text "Coda di stampa pulita in $printersPath ($($files.Count) file rimossi)" } Add-CleanerLog -Type 'Info' -Text "▶️ Riavvio servizio Spooler." - Start-Service -Name Spooler -ErrorAction Stop | Out-Null + Start-Service -Name Spooler -ErrorAction Stop *>$null Add-CleanerLog -Type 'Info' -Text "Servizio Spooler riavviato." Add-CleanerLog -Type 'Success' -Text "Print Queue Spooler pulito e riavviato con successo." } @@ -3550,6 +3317,7 @@ function WinCleaner { @{ Name = "SRUM Data"; Type = "File"; Paths = @("%SYSTEMROOT%\System32\sru\SRUDB.dat"); FilesOnly = $true; TakeOwnership = $true } @{ Name = "Start DPS"; Type = "Service"; ServiceName = "DPS"; Action = "Start" } @{ Name = "Listary Index"; Type = "File"; Paths = @("%APPDATA%\Listary\UserData"); PerUser = $true } + @{ Name = "WinUtil Data"; Type = "File"; Paths = @("%LOCALAPPDATA%\winutil"); PerUser = $true } @{ Name = "Flash Player Traces"; Type = "File"; Paths = @("%APPDATA%\Macromedia\Flash Player"); PerUser = $true } @{ Name = "Enhanced DiagTrack Management"; Type = "Custom"; ScriptBlock = { Add-CleanerLog -Type 'Info' -Text "🔄 Gestione migliorata servizio DiagTrack." @@ -3571,468 +3339,826 @@ function WinCleaner { function New-EmptyFile($Path) { $parentDirectory = [System.IO.Path]::GetDirectoryName($Path) if (-not (Test-Path $parentDirectory -PathType Container)) { - try { New-Item -ItemType Directory -Path $parentDirectory -Force -ErrorAction Stop | Out-Null } + try { New-Item -ItemType Directory -Path $parentDirectory -Force -ErrorAction Stop *>$null } catch { Write-StyledMessage -Type 'Warning' -Text "Failed to create parent directory: $_"; return $false } } - try { New-Item -ItemType File -Path $Path -Force -ErrorAction Stop | Out-Null; return $true } - catch { Write-StyledMessage -Type 'Warning' -Text "Failed to create file: $_"; return $false } + try { New-Item -ItemType File -Path $Path -Force -ErrorAction Stop *>$null; return $true } + catch { Write-StyledMessage -Type 'Warning' -Text "Failed to create file: $_"; return $false } + } + $serviceName = 'DiagTrack' + Add-CleanerLog -Type 'Info' -Text "Verifica stato servizio $serviceName." + $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + if (-not $service) { + Add-CleanerLog -Type 'Warning' -Text "Servizio $serviceName non trovato, skip" + return + } + if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running) { + Add-CleanerLog -Type 'Info' -Text "Servizio $serviceName attivo, arresto in corso." + try { + $service | Stop-Service -Force -ErrorAction Stop + $service.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Stopped, [TimeSpan]::FromSeconds(30)) + $path = Get-UniqueStateFilePath $serviceName + if (New-EmptyFile $path) { + Add-CleanerLog -Type 'Success' -Text "Servizio arrestato e stato salvato - riavvio automatico abilitato" + } + else { + Add-CleanerLog -Type 'Warning' -Text "Servizio arrestato - riavvio manuale richiesto" + } + } + catch { Write-StyledMessage -Type 'Warning' -Text "Errore durante arresto servizio: $_" } + } + else { + Add-CleanerLog -Type 'Info' -Text "Servizio $serviceName non attivo, verifica riavvio." + $fileGlob = Get-StateFilePath -BaseName $serviceName -Suffix '*' + $stateFiles = Get-ChildItem -Path $fileGlob -ErrorAction SilentlyContinue + if ($stateFiles.Count -eq 1) { + try { + Remove-Item -Path $stateFiles[0].FullName -Force -ErrorAction Stop + $service | Start-Service -ErrorAction Stop + Add-CleanerLog -Type 'Success' -Text "Servizio $serviceName riavviato con successo" + } + catch { Write-StyledMessage -Type 'Warning' -Text "Errore durante riavvio servizio: $_" } + } + elseif ($stateFiles.Count -gt 1) { + Add-CleanerLog -Type 'Info' -Text "Multiple state files found, servizio non verrà riavviato automaticamente" + } + else { + Add-CleanerLog -Type 'Info' -Text "Servizio $serviceName non era attivo precedentemente" + } + } + } + } + @{ Name = "Credential Manager"; Type = "Custom"; ScriptBlock = { + Add-CleanerLog -Type 'Info' -Text "🔑 Pulizia Credenziali." + $cmdkeyErr = $null + $targets = & cmdkey /list 2>&1 | Tee-Object -Variable cmdkeyErr | Where-Object { $_ -match '^Target:' } + if ($cmdkeyErr -and $LASTEXITCODE -ne 0) { Write-ToolkitLog -Level DEBUG -Message "cmdkey list error: $cmdkeyErr" } + $targets | ForEach-Object { + $t = $_.Split(':')[1].Trim() + $delErr = $null + & cmdkey /delete:$t 2>&1 | Tee-Object -Variable delErr *>$null + if ($delErr -and $LASTEXITCODE -ne 0) { Write-ToolkitLog -Level DEBUG -Message "cmdkey delete [$t] error: $delErr" } + } + } + } + @{ Name = "Regedit Last Key"; Type = "Registry"; Keys = @("HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Applets\Regedit"); ValuesOnly = $true } + @{ Name = "Windows.old"; Type = "ScriptBlock"; ScriptBlock = { + $path = "C:\Windows.old" + if (Test-Path $path) { + Add-CleanerLog -Type 'Info' -Text "🗑️ Rilevata cartella Windows.old. Avvio rimozione sicura con Native CleanMgr." + $regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Previous Installations" + if (-not (Test-Path $regKey)) { + Add-CleanerLog -Type 'Warning' -Text "Chiave registro 'Previous Installations' non trovata. Tentativo di esecuzione standard." + } + else { + try { + Set-ItemProperty -Path $regKey -Name "StateFlags0066" -Value 2 -Type DWORD -Force -ErrorAction Stop + Add-CleanerLog -Type 'Info' -Text "✅ Configurazione CleanMgr attivata per Windows.old (StateFlags0066)." + } + catch { + Add-CleanerLog -Type 'Warning' -Text "Impossibile scrivere nel registro per CleanMgr: $_" + } + } + $cleanMgrRule = @{ + Name = "Rimozione Windows.old (CleanMgr)"; + Type = "Command"; + Command = "cleanmgr.exe"; + Args = @("/sagerun:66"); + } + $null = Invoke-CommandAction -Rule $cleanMgrRule + if (Test-Path $path) { + Add-CleanerLog -Type 'Info' -Text "ℹ️ La cartella Windows.old potrebbe richiedere un riavvio per la rimozione completa." + } + else { + Add-CleanerLog -Type 'Success' -Text "✅ Windows.old rimosso con successo." + } + } + else { + Add-CleanerLog -Type 'Info' -Text "💭 Nessuna cartella Windows.old rilevata." + } + } + } + @{ Name = "Empty Recycle Bin"; Type = "Custom"; ScriptBlock = { + Clear-RecycleBin -Force -ErrorAction SilentlyContinue + Add-CleanerLog -Type 'Success' -Text "🗑️ Cestino svuotato" + } + } + ) + $totalRules = $Rules.Count + $currentRuleIndex = 0 + $successCount = 0 + $errorCount = 0 + foreach ($rule in $Rules) { + $currentRuleIndex++ + $percent = [math]::Round(($currentRuleIndex / $totalRules) * 100) + Write-ProgressUpdate -Activity "Esecuzione regole" -Status "$($rule.Name)" -Percent $percent -Icon '⚙️' + $result = Invoke-WinCleanerRule -Rule $rule + Clear-ProgressLine + if ($result) { $successCount++ } + else { $errorCount++ } + } + Clear-ProgressLine + Write-StyledMessage -Type 'Info' -Text "==================================================" + Write-StyledMessage -Type 'Info' -Text " RIEPILOGO OPERAZIONI " + Write-StyledMessage -Type 'Info' -Text "==================================================" + $stats = $script:WinCleanerLog | Group-Object Type + $sCount = ($stats | Where-Object Name -eq 'Success').Count + $wCount = ($stats | Where-Object Name -eq 'Warning').Count + $eCount = ($stats | Where-Object Name -eq 'Error').Count + Write-StyledMessage -Type 'Success' -Text "Operazioni completate con successo: $sCount." + if ($wCount -gt 0) { Write-StyledMessage -Type 'Warning' -Text "Avvisi generati: $wCount." } + if ($eCount -gt 0) { Write-StyledMessage -Type 'Error' -Text "Errori riscontrati: $eCount." } + Write-StyledMessage -Type 'Info' -Text "--------------------------------------------------" + Write-StyledMessage -Type 'Info' -Text "Dettaglio Errori e Warning:" + $problems = $script:WinCleanerLog | Where-Object { $_.Type -in 'Warning', 'Error' } + if ($problems) { + foreach ($p in $problems) { + Write-StyledMessage -Type $p.Type -Text $p.Text + } + } + else { + Write-StyledMessage -Type 'Success' -Text "Nessun problema rilevato." + } + Write-StyledMessage -Type 'Info' -Text "==================================================" + Invoke-ToolkitReboot -Message "Riavvio sistema in" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot +} +function DisableBitlocker { + [CmdletBinding()] + param( + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + Start-ToolkitSession -ToolName "DisableBitlocker" -SubTitle "Disable BitLocker Toolkit" + $regPath = $AppConfig.Registry.BitLocker + $timeout = 3600 + function Test-BitLockerStatus { + param([string]$DriveLetter = "C:") + try { return manage-bde -status $DriveLetter } + catch { + Write-StyledMessage -Type 'Warning' -Text "Impossibile verificare lo stato BitLocker: $($_.Exception.Message)" + return $null + } + } + try { + Write-StyledMessage -Type 'Info' -Text "🚀 Inizializzazione decrittazione drive C:." + $result = Invoke-WithSpinner -Activity "Disattivazione BitLocker" -Command 'manage-bde.exe' ` + -Arguments @('-off', 'C:') -TimeoutSeconds $timeout -LogContextKey "Bitlocker-Disable" + if ($result.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Decrittazione avviata/completata con successo." + Start-Sleep -Seconds 2 + $status = Test-BitLockerStatus -DriveLetter "C:" + if ($status -match "Decryption in progress" -or $status -match "Decriptazione in corso.") { + Write-StyledMessage -Type 'Info' -Text "⏳ Decrittazione in corso in background." + } + } + else { + Write-StyledMessage -Type 'Warning' -Text "⚠️ Codice uscita manage-bde: $($result.ExitCode). BitLocker potrebbe essere già disattivo o in errore." + } + Write-StyledMessage -Type 'Info' -Text "⚙️ Disabilitazione crittografia automatica nel registro." + Set-RegistryValue -Path $regPath -Name "PreventDeviceEncryption" -Value 1 + Write-StyledMessage -Type 'Success' -Text "🎉 Configurazione completata." + } + catch { + Write-ToolkitError -Record $_ -ToolName "DisableBitlocker" + } + finally { + Write-StyledMessage -Type 'Info' -Text "♻️ Pulizia risorse completata." + Invoke-ToolkitReboot -Message "Riavvio in" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot + Write-ToolkitLog -Level INFO -Message "DisableBitlocker sessione terminata." + } +} +function Install-Office { + [CmdletBinding()] + param( + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + Start-ToolkitSession -ToolName "OfficeInstall" -SubTitle "Office Install" + $tempDir = $AppConfig.Paths.OfficeTemp + function Set-OfficePostConfig { + Write-StyledMessage -Type 'Info' -Text "⚙️ Configurazione post-installazione Office." + foreach ($reg in @( + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "disconnectedstate"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "usercontentdisabled"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "downloadcontentdisabled"; Value = 1 }, + @{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 } + )) { Set-RegistryValue -Path $reg.Path -Name $reg.Name -Value $reg.Value } + Set-RegistryValue -Path "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\General" -Name "ShownOptIn" -Value 1 + Write-StyledMessage -Type 'Success' -Text "✅ Telemetria e Privacy Office disabilitate." + } + try { + Write-StyledMessage -Type 'Info' -Text "🏢 Avvio installazione Office Basic." + if (-not (Test-Path $tempDir)) { + $null = New-Item -ItemType Directory -Path $tempDir -Force + } + $setupPath = Join-Path $tempDir 'Setup.exe' + $configPath = Join-Path $tempDir 'Basic.xml' + foreach ($dl in @( + @{ Url = $AppConfig.URLs.OfficeSetup; Path = $setupPath; Name = 'Setup Office' }, + @{ Url = $AppConfig.URLs.OfficeBasicConfig; Path = $configPath; Name = 'Configurazione Basic' } + )) { + if (-not (Invoke-ToolkitDownload -Uri $dl.Url -OutputPath $dl.Path -Description $dl.Name)) { + Write-StyledMessage -Type 'Error' -Text "Download fallito. Installazione annullata." + return + } + } + Write-StyledMessage -Type 'Info' -Text "🚀 Avvio processo installazione." + $result = Invoke-WithSpinner -Activity "Installazione Office Basic" -Command $setupPath ` + -Arguments "/configure `"$configPath`"" -TimeoutSeconds 86400 -LogContextKey "Office-Install" + Clear-ProgressLine + if (-not $result.Success) { + Write-StyledMessage -Type 'Error' -Text "Installazione fallita." + return + } + Set-OfficePostConfig + Write-StyledMessage -Type 'Success' -Text "✅ Installazione completata." + Write-StyledMessage -Type 'Info' -Text "Riavvio non necessario." + } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante installazione Office: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore critico in Install-Office" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace + } + } + finally { + Remove-ItemSafely -Path $tempDir -Recurse + Write-StyledMessage -Type 'Success' -Text "🎯 Office Install terminato." + Write-ToolkitLog -Level INFO -Message "Install-Office sessione terminata." + } +} +function Repair-Office { + [CmdletBinding()] + param( + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + Start-ToolkitSession -ToolName "OfficeRepair" -SubTitle "Office Repair" + function Set-OfficePostConfig { + Write-StyledMessage -Type 'Info' -Text "⚙️ Configurazione post-riparazione Office." + foreach ($reg in @( + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "disconnectedstate"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "usercontentdisabled"; Value = 1 }, + @{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\office\16.0\common\privacy"; Name = "downloadcontentdisabled"; Value = 1 }, + @{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\office\16.0\common"; Name = "sendtelemetry"; Value = 0 } + )) { Set-RegistryValue -Path $reg.Path -Name $reg.Name -Value $reg.Value } + Set-RegistryValue -Path "HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\General" -Name "ShownOptIn" -Value 1 + Write-StyledMessage -Type 'Success' -Text "✅ Telemetria e Privacy Office disabilitate." + } + $needsReboot = $false + try { + Write-StyledMessage -Type 'Info' -Text "🔧 Avvio riparazione Office." + Stop-ToolkitProcesses -ProcessNames @('winword', 'excel', 'powerpnt', 'outlook', 'onenote', 'msaccess', 'visio', 'lync') + Write-StyledMessage -Type 'Info' -Text "🧹 Pulizia cache Office." + $cleanedCount = 0 + foreach ($cache in @( + "$env:LOCALAPPDATA\Microsoft\Office\16.0\Lync\Lync.cache", + "$env:LOCALAPPDATA\Microsoft\Office\16.0\OfficeFileCache" + )) { + if (Remove-ItemSafely -Path $cache -Recurse) { $cleanedCount++ } + } + if ($cleanedCount -gt 0) { Write-StyledMessage -Type 'Success' -Text "$cleanedCount cache eliminate." } + $officeClient = (Test-Path "${env:ProgramFiles}\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe") ? + "${env:ProgramFiles}\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe" : + "${env:ProgramFiles(x86)}\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe" + if (-not (Test-Path $officeClient)) { + Write-StyledMessage -Type 'Error' -Text "OfficeClickToRun.exe non trovato. Office potrebbe non essere installato." + return + } + try { + Write-StyledMessage -Type 'Info' -Text "🔧 Avvio riparazione rapida (offline)." + $null = Invoke-WithSpinner -Activity "Riparazione Rapida Office (Offline)" -Command $officeClient ` + -Arguments "scenario=Repair platform=x64 culture=it-it forceappshutdown=True RepairType=QuickRepair DisplayLevel=True" ` + -TimeoutSeconds 86400 -LogContextKey "Office-Repair-Quick" + Set-OfficePostConfig + Write-StyledMessage -Type 'Success' -Text "🎉 Riparazione Office completata!" + $needsReboot = $true + } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante riparazione rapida: $($_.Exception.Message)." + try { + Write-StyledMessage -Type 'Info' -Text "🌐 Tentativo riparazione completa (online) come fallback." + $null = Invoke-WithSpinner -Activity "Riparazione Completa Office (Online)" -Command $officeClient ` + -Arguments "scenario=Repair platform=x64 culture=it-it forceappshutdown=True RepairType=FullRepair DisplayLevel=True" ` + -TimeoutSeconds 86400 -LogContextKey "Office-Repair-Full" + Set-OfficePostConfig + Write-StyledMessage -Type 'Success' -Text "🎉 Riparazione Office completata!" + $needsReboot = $true + } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore anche durante riparazione online: $($_.Exception.Message)." + } + } + } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore critico durante riparazione Office: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore critico in Repair-Office" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace + } + } + finally { + Write-StyledMessage -Type 'Success' -Text "🎯 Office Repair terminato." + Write-ToolkitLog -Level INFO -Message "Repair-Office sessione terminata." + } + if ($needsReboot) { + Invoke-ToolkitReboot -Message "Riparazione completata" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot + } +} +function Uninstall-Office { + [CmdletBinding()] + param( + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + Start-ToolkitSession -ToolName "OfficeUninstall" -SubTitle "Office Uninstall" + $tempDir = $AppConfig.Paths.OfficeTemp + function Get-WindowsVersion { + try { + $buildNumber = [int](Get-CimInstance -ClassName Win32_OperatingSystem).BuildNumber + return $buildNumber -ge 22631 ? "Windows11_23H2_Plus" : ($buildNumber -ge 22000 ? "Windows11_22H2_Or_Older" : "Windows10_Or_Older") + } + catch { + Write-StyledMessage -Type 'Warning' -Text "Impossibile rilevare versione Windows: $_" + return "Unknown" + } + } + function Remove-ItemsSilently { + param([string[]]$Paths, [string]$ItemType = "cartella") + $removed = @() + $failed = @() + foreach ($path in $Paths) { + if (Test-Path $path) { + if (Remove-ItemSafely -Path $path -Recurse) { $removed += $path } + else { $failed += $path } + } + } + return @{ Removed = $removed; Failed = $failed; Count = $removed.Count } + } + function Remove-OfficeDirectly { + Write-StyledMessage -Type 'Info' -Text "🔧 Avvio rimozione diretta Office." + try { + Write-StyledMessage -Type 'Info' -Text "📋 Ricerca installazioni Office." + $officePackages = Get-Package -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like "*Microsoft Office*" -or $_.Name -like "*Microsoft 365*" -or $_.Name -like "*Office*" } + if ($officePackages) { + Write-StyledMessage -Type 'Info' -Text "Trovati $($officePackages.Count) pacchetti Office." + foreach ($package in $officePackages) { + try { + $null = Uninstall-Package -Name $package.Name -Force -ErrorAction Stop + Write-StyledMessage -Type 'Success' -Text "Rimosso: $($package.Name)." + } + catch {} + } + } + Write-StyledMessage -Type 'Info' -Text "🔍 Ricerca nel registro." + foreach ($keyPath in @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*", + "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" + )) { + try { + $items = Get-ItemProperty -Path $keyPath -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -like "*Office*" -or $_.DisplayName -like "*Microsoft 365*" } + foreach ($item in $items) { + if ($item.UninstallString -and $item.UninstallString -match "msiexec") { + try { + $null = Invoke-WithSpinner -Activity "Rimozione: $($item.DisplayName)" -Command 'msiexec.exe' ` + -Arguments @('/x', $item.PSChildName, '/qn', '/norestart') -TimeoutSeconds 1800 ` + -LogContextKey "Office-Uninstall-MSI-$($item.PSChildName)" + } + catch {} + } + } } - $serviceName = 'DiagTrack' - Add-CleanerLog -Type 'Info' -Text "Verifica stato servizio $serviceName." + catch {} + } + Write-StyledMessage -Type 'Info' -Text "🛑 Arresto servizi Office." + $stoppedServices = 0 + foreach ($serviceName in @('ClickToRunSvc', 'OfficeSvc', 'OSE')) { $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue - if (-not $service) { - Add-CleanerLog -Type 'Warning' -Text "Servizio $serviceName non trovato, skip" - return - } - if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running) { - Add-CleanerLog -Type 'Info' -Text "Servizio $serviceName attivo, arresto in corso." + if ($service) { try { - $service | Stop-Service -Force -ErrorAction Stop - $service.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Stopped, [TimeSpan]::FromSeconds(30)) - $path = Get-UniqueStateFilePath $serviceName - if (New-EmptyFile $path) { - Add-CleanerLog -Type 'Success' -Text "Servizio arrestato e stato salvato - riavvio automatico abilitato" - } - else { - Add-CleanerLog -Type 'Warning' -Text "Servizio arrestato - riavvio manuale richiesto" - } + Stop-Service -Name $serviceName -Force -ErrorAction Stop + Set-Service -Name $serviceName -StartupType Disabled -ErrorAction Stop + Write-StyledMessage -Type 'Success' -Text "Servizio arrestato: $serviceName." + $stoppedServices++ } - catch { Write-StyledMessage -Type 'Warning' -Text "Errore durante arresto servizio: $_" } + catch {} } - else { - Add-CleanerLog -Type 'Info' -Text "Servizio $serviceName non attivo, verifica riavvio." - $fileGlob = Get-StateFilePath -BaseName $serviceName -Suffix '*' - $stateFiles = Get-ChildItem -Path $fileGlob -ErrorAction SilentlyContinue - if ($stateFiles.Count -eq 1) { - try { - Remove-Item -Path $stateFiles[0].FullName -Force -ErrorAction Stop - $service | Start-Service -ErrorAction Stop - Add-CleanerLog -Type 'Success' -Text "Servizio $serviceName riavviato con successo" + } + Write-StyledMessage -Type 'Info' -Text "🧹 Pulizia cartelle Office." + $folderResult = Remove-ItemsSilently -Paths @( + "$env:ProgramFiles\Microsoft Office", + "${env:ProgramFiles(x86)}\Microsoft Office", + "$env:ProgramFiles\Microsoft Office 15", + "${env:ProgramFiles(x86)}\Microsoft Office 15", + "$env:ProgramFiles\Microsoft Office 16", + "${env:ProgramFiles(x86)}\Microsoft Office 16", + "$env:ProgramData\Microsoft\Office", + "$env:LOCALAPPDATA\Microsoft\Office", + "$env:ProgramFiles\Common Files\Microsoft Shared\ClickToRun", + "${env:ProgramFiles(x86)}\Common Files\Microsoft Shared\ClickToRun" + ) -ItemType "cartella" + if ($folderResult.Count -gt 0) { Write-StyledMessage -Type 'Success' -Text "$($folderResult.Count) cartelle Office rimosse." } + if ($folderResult.Failed.Count -gt 0) { Write-StyledMessage -Type 'Warning' -Text "Impossibile rimuovere $($folderResult.Failed.Count) cartelle (potrebbero essere in uso)." } + Write-StyledMessage -Type 'Info' -Text "🔧 Pulizia registro Office." + $regResult = Remove-ItemsSilently -Paths @( + "HKCU:\Software\Microsoft\Office", + "HKLM:\SOFTWARE\Microsoft\Office", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Office", + "HKCU:\Software\Microsoft\Office\16.0", + "HKLM:\SOFTWARE\Microsoft\Office\16.0", + "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun", + "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Office\ClickToRun" + ) -ItemType "chiave" + if ($regResult.Count -gt 0) { Write-StyledMessage -Type 'Success' -Text "$($regResult.Count) chiavi registro Office rimosse." } + Write-StyledMessage -Type 'Info' -Text "📅 Pulizia attività pianificate." + $tasksRemoved = 0 + try { + $officeTasks = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like "*Office*" } + foreach ($task in $officeTasks) { + try { Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction Stop; $tasksRemoved++ } + catch {} + } + if ($tasksRemoved -gt 0) { Write-StyledMessage -Type 'Success' -Text "$tasksRemoved attività Office rimosse." } + } + catch {} + Write-StyledMessage -Type 'Info' -Text "🖥️ Rimozione collegamenti Office." + $shortcutsRemoved = 0 + foreach ($desktopPath in @( + $AppConfig.Paths.Desktop, + "$env:PUBLIC\Desktop", + "$env:APPDATA\Microsoft\Windows\Start Menu\Programs", + "$env:ALLUSERSPROFILE\Microsoft\Windows\Start Menu\Programs" + )) { + if (Test-Path $desktopPath) { + foreach ($shortcut in @( + "Microsoft Word*.lnk", "Microsoft Excel*.lnk", "Microsoft PowerPoint*.lnk", + "Microsoft Outlook*.lnk", "Microsoft OneNote*.lnk", "Microsoft Access*.lnk", + "Office*.lnk", "Word*.lnk", "Excel*.lnk", "PowerPoint*.lnk", "Outlook*.lnk" + )) { + foreach ($file in (Get-ChildItem -Path $desktopPath -Filter $shortcut -Recurse -ErrorAction SilentlyContinue)) { + if (Remove-ItemSafely -Path $file.FullName) { $shortcutsRemoved++ } } - catch { Write-StyledMessage -Type 'Warning' -Text "Errore durante riavvio servizio: $_" } - } - elseif ($stateFiles.Count -gt 1) { - Add-CleanerLog -Type 'Info' -Text "Multiple state files found, servizio non verrà riavviato automaticamente" - } - else { - Add-CleanerLog -Type 'Info' -Text "Servizio $serviceName non era attivo precedentemente" } } } + if ($shortcutsRemoved -gt 0) { Write-StyledMessage -Type 'Success' -Text "$shortcutsRemoved collegamenti Office rimossi." } + Write-StyledMessage -Type 'Info' -Text "💽 Pulizia residui Office." + $null = Remove-ItemsSilently -Paths @( + "$env:LOCALAPPDATA\Microsoft\OneDrive", + "$env:APPDATA\Microsoft\OneDrive", + "$env:TEMP\Office*", + "$env:TEMP\MSO*" + ) -ItemType "residuo" + Write-StyledMessage -Type 'Success' -Text "✅ Rimozione diretta completata." + Write-StyledMessage -Type 'Info' -Text "📊 Riepilogo: $($folderResult.Count) cartelle, $($regResult.Count) chiavi registro, $shortcutsRemoved collegamenti, $tasksRemoved attività rimosse." + return $true } - @{ Name = "Credential Manager"; Type = "Custom"; ScriptBlock = { - Add-CleanerLog -Type 'Info' -Text "🔑 Pulizia Credenziali." - $cmdkeyErr = $null - $targets = & cmdkey /list 2>&1 | Tee-Object -Variable cmdkeyErr | Where-Object { $_ -match '^Target:' } - if ($cmdkeyErr -and $LASTEXITCODE -ne 0) { Write-ToolkitLog -Level DEBUG -Message "cmdkey list error: $cmdkeyErr" } - $targets | ForEach-Object { - $t = $_.Split(':')[1].Trim() - $delErr = $null - & cmdkey /delete:$t 2>&1 | Tee-Object -Variable delErr | Out-Null - if ($delErr -and $LASTEXITCODE -ne 0) { Write-ToolkitLog -Level DEBUG -Message "cmdkey delete [$t] error: $delErr" } - } - } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante rimozione diretta Office: $($_.Exception.Message)." + return $false } - @{ Name = "Regedit Last Key"; Type = "Registry"; Keys = @("HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Applets\Regedit"); ValuesOnly = $true } - @{ Name = "Windows.old"; Type = "ScriptBlock"; ScriptBlock = { - $path = "C:\Windows.old" - if (Test-Path $path) { - Add-CleanerLog -Type 'Info' -Text "🗑️ Rilevata cartella Windows.old. Avvio rimozione sicura con Native CleanMgr." - $regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Previous Installations" - if (-not (Test-Path $regKey)) { - Add-CleanerLog -Type 'Warning' -Text "Chiave registro 'Previous Installations' non trovata. Tentativo di esecuzione standard." - } - else { - try { - Set-ItemProperty -Path $regKey -Name "StateFlags0066" -Value 2 -Type DWORD -Force -ErrorAction Stop - Add-CleanerLog -Type 'Info' -Text "✅ Configurazione CleanMgr attivata per Windows.old (StateFlags0066)." - } - catch { - Add-CleanerLog -Type 'Warning' -Text "Impossibile scrivere nel registro per CleanMgr: $_" + } + function Start-OfficeUninstallWithGetHelp { + try { + if (-not (Test-Path $tempDir)) { $null = New-Item -ItemType Directory -Path $tempDir -Force } + $getHelpZipPath = Join-Path $tempDir 'GetHelp.zip' + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.GetHelpInstaller -OutputPath $getHelpZipPath -Description 'Microsoft Get Help')) { + return $false + } + Write-StyledMessage -Type 'Info' -Text "📦 Estrazione Get Help." + try { + Expand-Archive -Path $getHelpZipPath -DestinationPath $tempDir -Force + Write-StyledMessage -Type 'Success' -Text "Estrazione completata." + } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante estrazione archivio Get Help: $($_.Exception.Message)." + return $false + } + $getHelpExe = Get-ChildItem -Path $tempDir -Filter "GetHelpCmd.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $getHelpExe) { + Write-StyledMessage -Type 'Error' -Text "GetHelpCmd.exe non trovato." + return $false + } + Write-StyledMessage -Type 'Info' -Text "🚀 Rimozione tramite Get Help." + Write-StyledMessage -Type 'Warning' -Text "⏰ Questa operazione può richiedere alcuni minuti." + try { + $result = Invoke-WithSpinner -Activity "Rimozione Office tramite Get Help" -Command $getHelpExe.FullName ` + -Arguments '-S OfficeScrubScenario -AcceptEula' ` + -TimeoutSeconds 86400 -LogContextKey "Office-Uninstall-GetHelp" + $outputStr = $result.StdOut + $result.StdErr + $isInvalidArgs = $outputStr -match "Error: Invalid command line arguments" -or $outputStr -match "Usage: GetHelpCmd\.exe" + if ($result.ExitCode -eq 0 -and -not $isInvalidArgs) { + $blockingProcesses = @('Setup', 'GetHelpCmd', 'OfficeClickToRun', 'Integrator', 'OfficeScrub', 'cscript') + $waitStart = Get-Date + Start-Sleep -Seconds 12 + if (Get-Process -Name $blockingProcesses -ErrorAction SilentlyContinue) { + Write-StyledMessage -Type 'Info' -Text "⏳ Get Help ha avviato la rimozione in una finestra esterna. Attesa completamento..." + $spinnerIndex = 0 + while ((Get-Process -Name $blockingProcesses -ErrorAction SilentlyContinue) -and ((Get-Date) - $waitStart).TotalSeconds -lt 2700) { + $elapsed = [math]::Round(((Get-Date) - $waitStart).TotalSeconds, 1) + $spinner = if ($Global:Spinners) { $Global:Spinners[$spinnerIndex++ % $Global:Spinners.Length] } else { '' } + Write-ProgressUpdate -Activity "Rimozione Office" -Status "In corso... ($elapsed secondi)" -Percent 90 -Icon '⏳' -Spinner $spinner + Start-Sleep -Milliseconds 500 } + Clear-ProgressLine } - $cleanMgrRule = @{ - Name = "Rimozione Windows.old (CleanMgr)"; - Type = "Command"; - Command = "cleanmgr.exe"; - Args = @("/sagerun:66"); - } - $result = Invoke-CommandAction -Rule $cleanMgrRule - if (Test-Path $path) { - Add-CleanerLog -Type 'Info' -Text "ℹ️ La cartella Windows.old potrebbe richiedere un riavvio per la rimozione completa." - } - else { - Add-CleanerLog -Type 'Success' -Text "✅ Windows.old rimosso con successo." - } + Write-StyledMessage -Type 'Success' -Text "✅ Get Help completato con successo." + return $true } else { - Add-CleanerLog -Type 'Info' -Text "💭 Nessuna cartella Windows.old rilevata." + $reason = if ($isInvalidArgs) { "Parametri non supportati dalla versione del tool" } else { "Codice uscita: $($result.ExitCode)" } + Write-StyledMessage -Type 'Warning' -Text "Get Help fallito: $reason. Tentativo metodo alternativo." + return Remove-OfficeDirectly } } + catch { + Write-StyledMessage -Type 'Warning' -Text "Errore durante esecuzione Get Help: $($_.Exception.Message). Passaggio a metodo alternativo." + return Remove-OfficeDirectly + } } - @{ Name = "Empty Recycle Bin"; Type = "Custom"; ScriptBlock = { - Clear-RecycleBin -Force -ErrorAction SilentlyContinue - Add-CleanerLog -Type 'Success' -Text "🗑️ Cestino svuotato" + catch { + Write-StyledMessage -Type 'Warning' -Text "Errore durante processo Get Help: $($_.Exception.Message)." + return $false + } + finally { + Remove-ItemSafely -Path $tempDir -Recurse + } + } + $needsReboot = $false + try { + Write-StyledMessage -Type 'Warning' -Text "🗑️ Avvio rimozione completa Microsoft Office." + Stop-ToolkitProcesses -ProcessNames @('winword', 'excel', 'powerpnt', 'outlook', 'onenote', 'msaccess', 'visio', 'lync') + Write-StyledMessage -Type 'Info' -Text "🔍 Rilevamento versione Windows." + $windowsVersion = Get-WindowsVersion + Write-StyledMessage -Type 'Info' -Text "🎯 Versione rilevata: $windowsVersion." + $success = switch ($windowsVersion) { + 'Windows11_23H2_Plus' { + Write-StyledMessage -Type 'Info' -Text "🚀 Utilizzo metodo Get Help per Windows 11 23H2+." + Start-OfficeUninstallWithGetHelp + } + default { + Write-StyledMessage -Type 'Info' -Text "⚡ Utilizzo rimozione diretta per Windows 11 22H2 o precedenti." + Remove-OfficeDirectly } } - ) - $totalRules = $Rules.Count - $currentRuleIndex = 0 - $successCount = 0 - $warningCount = 0 - $errorCount = 0 - foreach ($rule in $Rules) { - $currentRuleIndex++ - $percent = [math]::Round(($currentRuleIndex / $totalRules) * 100) - Clear-ProgressLine - Show-ProgressBar -Activity "Esecuzione regole" -Status "$($rule.Name)" -Percent $percent -Icon '⚙️' - $result = Invoke-WinCleanerRule -Rule $rule - Clear-ProgressLine - if ($result) { - $successCount++ + Write-Progress -Activity "Rimozione" -Completed -ErrorAction SilentlyContinue + Write-Host "" + Write-Host "" + if ($success) { + Write-StyledMessage -Type 'Success' -Text "🎉 Rimozione Office completata!" + $needsReboot = $true } else { - $errorCount++ + Write-StyledMessage -Type 'Error' -Text "Rimozione non completata." + Write-StyledMessage -Type 'Info' -Text "💡 Puoi provare un metodo alternativo o rimozione manuale." } } - Clear-ProgressLine - Write-Host "`n" - Write-StyledMessage -Type 'Info' -Text "==================================================" - Write-StyledMessage -Type 'Info' -Text " RIEPILOGO OPERAZIONI " - Write-StyledMessage -Type 'Info' -Text "==================================================" - $stats = $global:WinCleanerLog | Group-Object Type - $sCount = ($stats | Where-Object Name -eq 'Success').Count - $wCount = ($stats | Where-Object Name -eq 'Warning').Count - $eCount = ($stats | Where-Object Name -eq 'Error').Count - Write-StyledMessage -Type 'Success' -Text "Operazioni completate con successo: $sCount." - if ($wCount -gt 0) { Write-StyledMessage -Type 'Warning' -Text "Avvisi generati: $wCount." } - if ($eCount -gt 0) { Write-StyledMessage -Type 'Error' -Text "Errori riscontrati: $eCount." } - Write-StyledMessage -Type 'Info' -Text "--------------------------------------------------" - Write-StyledMessage -Type 'Info' -Text "Dettaglio Errori e Warning:" - $problems = $global:WinCleanerLog | Where-Object { $_.Type -in 'Warning', 'Error' } - if ($problems) { - foreach ($p in $problems) { - Write-StyledMessage -Type $p.Type -Text $p.Text + catch { + Write-StyledMessage -Type 'Error' -Text "Errore critico durante rimozione Office: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore critico in Uninstall-Office" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace } } - else { - Write-StyledMessage -Type 'Success' -Text "Nessun problema rilevato." - } - Write-StyledMessage -Type 'Info' -Text "==================================================" - Write-Host "`n" - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio individuale soppresso. Verrà gestito un riavvio finale." + finally { + Write-StyledMessage -Type 'Success' -Text "🧹 Pulizia finale." + Remove-ItemSafely -Path $tempDir -Recurse + Write-StyledMessage -Type 'Success' -Text "🎯 Office Uninstall terminato." + Write-ToolkitLog -Level INFO -Message "Uninstall-Office sessione terminata." } - else { - $shouldReboot = Start-InterruptibleCountdown -Seconds $CountdownSeconds -Message "Riavvio sistema in" - if ($shouldReboot) { - Restart-Computer -Force - } + if ($needsReboot) { + Invoke-ToolkitReboot -Message "Rimozione completata" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot } } -function VideoDriverInstall { +function AutoVideoDriverInstall { [CmdletBinding()] param( - [Parameter(Mandatory = $false)] [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "VideoDriverInstall" - Show-Header -SubTitle "Video Driver Install Toolkit" - $Host.UI.RawUI.WindowTitle = "Video Driver Install Toolkit By MagnetarMan" - $GitHubAssetBaseUrl = $AppConfig.URLs.GitHubAssetBaseUrl - $DriverToolsLocalPath = $AppConfig.Paths.Drivers - $DesktopPath = [Environment]::GetFolderPath('Desktop') - function Get-GpuManufacturer { - $pnpDevices = Get-PnpDevice -Class Display -ErrorAction SilentlyContinue - if (-not $pnpDevices) { - Write-StyledMessage Warning "Nessun dispositivo display Plug and Play rilevato." - return 'Unknown' - } - foreach ($device in $pnpDevices) { - $manufacturer = $device.Manufacturer - $friendlyName = $device.FriendlyName - if ($friendlyName -match 'NVIDIA|GeForce|Quadro|Tesla' -or $manufacturer -match 'NVIDIA') { - return 'NVIDIA' - } - elseif ($friendlyName -match 'AMD|Radeon|ATI' -or $manufacturer -match 'AMD|ATI') { - return 'AMD' - } - elseif ($friendlyName -match 'Intel|Iris|UHD|HD Graphics' -or $manufacturer -match 'Intel') { - return 'Intel' - } - } - return 'Unknown' - } + Start-ToolkitSession -ToolName "AutoVideoDriverInstall" -SubTitle "Auto Video Driver Install" + $desktopPath = $AppConfig.Paths.Desktop function Set-BlockWindowsUpdateDrivers { - Write-StyledMessage Info "Configurazione per bloccare download driver da Windows Update." - $regPath = $AppConfig.Registry.WindowsUpdatePolicies - $propertyName = "ExcludeWUDriversInQualityUpdate" - $propertyValue = 1 + Write-StyledMessage -Type 'Info' -Text "Blocco driver automatici da Windows Update." try { - if (-not (Test-Path $regPath)) { - New-Item -Path $regPath -Force | Out-Null + Set-RegistryValue -Path $AppConfig.Registry.WindowsUpdatePolicies -Name "ExcludeWUDriversInQualityUpdate" -Value 1 + Write-StyledMessage -Type 'Success' -Text "Blocco WU driver impostato." + $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo (può impiegare 1-2 minuti)" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" -TimeoutSeconds 180 + if ($gpupdateResult -and $gpupdateResult.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Criteri di gruppo aggiornati." } - Set-ItemProperty -Path $regPath -Name $propertyName -Value $propertyValue -Type DWord -Force -ErrorAction Stop - Write-StyledMessage Success "Blocco download driver da Windows Update impostato correttamente nel registro." - Write-StyledMessage Info "Questa impostazione impedisce a Windows Update di installare driver automaticamente." - } - catch { - Write-StyledMessage Error "Errore durante l'impostazione del blocco download driver da Windows Update: $($_.Exception.Message)." - Write-StyledMessage Warning "Potrebbe essere necessario eseguire lo script come amministratore." - return - } - Write-StyledMessage Info "Aggiornamento dei criteri di gruppo in corso per applicare le modifiche." - try { - $procParams = @{ - FilePath = 'gpupdate.exe' - ArgumentList = '/force' - Wait = $true - NoNewWindow = $true - PassThru = $true - ErrorAction = 'Stop' - } - $gpupdateProcess = Start-Process @procParams - if ($gpupdateProcess.ExitCode -eq 0) { - Write-StyledMessage Success "Criteri di gruppo aggiornati con successo." + elseif ($gpupdateResult) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate completato con codice: $($gpupdateResult.ExitCode). Proseguo comunque." } else { - Write-StyledMessage Warning "Aggiornamento dei criteri di gruppo completato con codice di uscita non zero: $($gpupdateProcess.ExitCode)." + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate non ha risposto. Proseguo comunque." } } catch { - Write-StyledMessage Error "Errore durante l'aggiornamento dei criteri di gruppo: $($_.Exception.Message)." - Write-StyledMessage Warning "Le modifiche ai criteri potrebbero richiedere un riavvio o del tempo per essere applicate." + Write-StyledMessage -Type 'Warning' -Text "⚠️ Errore blocco WU driver: $($_.Exception.Message). Proseguo comunque." } } - function Download-FileWithProgress { - param( - [Parameter(Mandatory = $true)] - [string]$Url, - [Parameter(Mandatory = $true)] - [string]$DestinationPath, - [Parameter(Mandatory = $true)] - [string]$Description, - [int]$MaxRetries = 3 - ) - Write-StyledMessage Info "Scaricando $Description." - $destDir = Split-Path -Path $DestinationPath -Parent - if (-not (Test-Path $destDir)) { - try { - New-Item -ItemType Directory -Path $destDir -Force | Out-Null - } - catch { - Write-StyledMessage Error "Impossibile creare la cartella di destinazione '$destDir': $($_.Exception.Message)." - return $false + try { + Write-StyledMessage -Type 'Info' -Text "🚀 Avvio installazione automatica driver video." + Set-BlockWindowsUpdateDrivers + Write-StyledMessage -Type 'Info' -Text "🔍 Rilevamento configurazione GPU in corso..." + $gpuAnalysis = VcardAnalizer + $gpuManufacturer = $gpuAnalysis.PrimaryManufacturer + Write-StyledMessage -Type 'Info' -Text "GPU rilevata: $gpuManufacturer." + $stableDownloadDone = $false + if ($gpuAnalysis.Matches.Count -gt 0) { + foreach ($match in $gpuAnalysis.Matches) { + if ([string]::IsNullOrWhiteSpace($match.DownloadUrl)) { continue } + $targetName = if (-not [string]::IsNullOrWhiteSpace($match.FileName)) { $match.FileName } else { "$($match.Key).exe" } + $targetPath = Join-Path $desktopPath $targetName + $displayName = if (-not [string]::IsNullOrWhiteSpace($match.DisplayName)) { $match.DisplayName } else { $match.Key } + if (Invoke-ToolkitDownload -Uri $match.DownloadUrl -OutputPath $targetPath -Description $displayName) { + Write-StyledMessage -Type 'Success' -Text "Driver stabile scaricato sul desktop: $displayName" + $stableDownloadDone = $true + } } } - for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { - try { - $webRequest = [System.Net.WebRequest]::Create($Url) - $webResponse = $webRequest.GetResponse() - $totalBytes = $webResponse.ContentLength - $responseStream = $webResponse.GetResponseStream() - $targetStream = [System.IO.FileStream]::new($DestinationPath, [System.IO.FileMode]::Create) - $buffer = New-Object byte[] 64KB - $downloadedBytes = 0 - $bytesRead = 0 - Write-Progress -Activity "Download $Description" -Status "Inizio download." -PercentComplete 0 - do { - $bytesRead = $responseStream.Read($buffer, 0, $buffer.Length) - if ($bytesRead -gt 0) { - $targetStream.Write($buffer, 0, $bytesRead) - $downloadedBytes += $bytesRead - $percentComplete = [System.Math]::Round(($downloadedBytes / $totalBytes) * 100, 1) - $speed = if ($downloadedBytes -gt 0) { [System.Math]::Round(($downloadedBytes / 1024 / 1024), 2) } else { 0 } - $totalSize = [System.Math]::Round(($totalBytes / 1024 / 1024), 2) - Write-Progress -Activity "Download $Description" -Status "$speed MB / $totalSize MB" -PercentComplete $percentComplete + if (-not $stableDownloadDone) { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato. Uso fallback autodetect." + switch ($gpuManufacturer) { + 'AMD' { + $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare installer AMD. Annullamento." + return } - } while ($bytesRead -gt 0) - Write-Progress -Activity "Download $Description" -Status "Completato" -PercentComplete 100 -Completed - $targetStream.Flush() - $targetStream.Close() - $targetStream.Dispose() - $responseStream.Dispose() - $webResponse.Close() - Write-StyledMessage Success "Download di $Description completato." - return $true - } - catch { - Write-Progress -Activity "Download $Description" -Completed - Write-StyledMessage Warning "Tentativo $attempt fallito per $Description`: $($_.Exception.Message)." - if ($attempt -lt $MaxRetries) { - Start-Sleep -Seconds 2 + } + 'NVIDIA' { + $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare NVCleanstall. Annullamento." + return + } + } + 'Intel' { + Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." + } + default { + Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: driver non disponibile per l'installazione automatica." } } } - Write-StyledMessage Error "Errore durante il download di $Description dopo $MaxRetries tentativi." - return $false } - function Handle-InstallVideoDrivers { - Write-StyledMessage Info "Opzione 1: Avvio installazione driver video." - $gpuManufacturer = Get-GpuManufacturer - Write-StyledMessage Info "Rilevata GPU: $gpuManufacturer." - if ($gpuManufacturer -eq 'AMD') { - $amdInstallerUrl = $AppConfig.URLs.AMDInstaller - $amdInstallerPath = Join-Path $DriverToolsLocalPath "AMD-Autodetect.exe" - if (Download-FileWithProgress -Url $amdInstallerUrl -DestinationPath $amdInstallerPath -Description "AMD Auto-Detect Tool") { - Write-StyledMessage Info "Avvio installazione driver video AMD. Premi un tasto per chiudere correttamente il terminale quando l'installazione è completata." - $procParams = @{ - FilePath = $amdInstallerPath - Wait = $true - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams - Write-StyledMessage Success "Installazione driver video AMD completata o chiusa." - } + catch { + Write-StyledMessage -Type 'Error' -Text "Errore durante installazione driver: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore in AutoVideoDriverInstall" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace } - elseif ($gpuManufacturer -eq 'NVIDIA') { - $nvidiaInstallerUrl = $AppConfig.URLs.NVCleanstall - $nvidiaInstallerPath = Join-Path $DriverToolsLocalPath "NVCleanstall_1.19.0.exe" - if (Download-FileWithProgress -Url $nvidiaInstallerUrl -DestinationPath $nvidiaInstallerPath -Description "NVCleanstall Tool") { - Write-StyledMessage Info "Avvio installazione driver video NVIDIA Ottimizzato. Premi un tasto per chiudere correttamente il terminale quando l'installazione è completata." - $procParams = @{ - FilePath = $nvidiaInstallerPath - Wait = $true - ErrorAction = 'SilentlyContinue' - } - Start-Process @procParams - Write-StyledMessage Success "Installazione driver video NVIDIA completata o chiusa." + } + finally { + Write-StyledMessage -Type 'Success' -Text "🎯 Auto Video Driver Install terminato." + Write-ToolkitLog -Level INFO -Message "AutoVideoDriverInstall sessione terminata." + } +} +function VideoDriverReinstall { + [CmdletBinding()] + param( + [int]$CountdownSeconds = 30, + [switch]$SuppressIndividualReboot + ) + Start-ToolkitSession -ToolName "VideoDriverReinstall" -SubTitle "Video Driver Reinstall" + $driverToolsPath = $AppConfig.Paths.Drivers + $desktopPath = $AppConfig.Paths.Desktop + function Set-BlockWindowsUpdateDrivers { + Write-StyledMessage -Type 'Info' -Text "Blocco driver automatici da Windows Update." + try { + Set-RegistryValue -Path $AppConfig.Registry.WindowsUpdatePolicies -Name "ExcludeWUDriversInQualityUpdate" -Value 1 + Write-StyledMessage -Type 'Success' -Text "Blocco WU driver impostato." + $gpupdateResult = Invoke-WithSpinner -Activity "Aggiornamento criteri di gruppo (può impiegare 1-2 minuti)" -Command 'gpupdate.exe' -Arguments '/force' -LogContextKey "Video-GPUpdate" -TimeoutSeconds 180 + if ($gpupdateResult -and $gpupdateResult.ExitCode -eq 0) { + Write-StyledMessage -Type 'Success' -Text "✅ Criteri di gruppo aggiornati." + } + elseif ($gpupdateResult) { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate completato con codice: $($gpupdateResult.ExitCode). Proseguo comunque." + } + else { + Write-StyledMessage -Type 'Warning' -Text "⚠️ gpupdate non ha risposto. Proseguo comunque." } } - elseif ($gpuManufacturer -eq 'Intel') { - Write-StyledMessage Info "Rilevata GPU Intel. Utilizza Windows Update per aggiornare i driver integrati." - } - else { - Write-StyledMessage Error "Produttore GPU non supportato o non rilevato per l'installazione automatica dei driver." + catch { + Write-StyledMessage -Type 'Warning' -Text "⚠️ Errore blocco WU driver: $($_.Exception.Message). Proseguo comunque." } } - function Handle-ReinstallRepairVideoDrivers { - Write-StyledMessage Warning "Opzione 2: Avvio procedura di reinstallazione/riparazione driver video. Richiesto riavvio." - $dduZipUrl = $AppConfig.URLs.DDUZip - $dduZipPath = Join-Path $DriverToolsLocalPath "DDU.zip" - if (-not (Download-FileWithProgress -Url $dduZipUrl -DestinationPath $dduZipPath -Description "DDU (Display Driver Uninstaller)")) { - Write-StyledMessage Error "Impossibile scaricare DDU. Annullamento operazione." + $needsReboot = $false + try { + Write-StyledMessage -Type 'Warning' -Text "🔧 Avvio procedura reinstallazione/riparazione driver video." + Set-BlockWindowsUpdateDrivers + Write-StyledMessage -Type 'Info' -Text "📥 Preparazione download strumenti necessari..." + $dduZipPath = Join-Path $driverToolsPath "DDU.zip" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.DDUZip -OutputPath $dduZipPath -Description "DDU (Display Driver Uninstaller)")) { + Write-StyledMessage -Type 'Error' -Text "Impossibile scaricare DDU. Annullamento." return } - Write-StyledMessage Info "Estrazione DDU sul Desktop." + Write-StyledMessage -Type 'Info' -Text "Estrazione DDU sul Desktop." try { - Expand-Archive -Path $dduZipPath -DestinationPath $DesktopPath -Force - Write-StyledMessage Success "DDU estratto correttamente sul Desktop." + Expand-Archive -Path $dduZipPath -DestinationPath $desktopPath -Force + Write-StyledMessage -Type 'Success' -Text "DDU estratto sul Desktop." } catch { - Write-StyledMessage Error "Errore durante l'estrazione di DDU sul Desktop: $($_.Exception.Message)." + Write-StyledMessage -Type 'Error' -Text "Errore estrazione DDU: $($_.Exception.Message)." return } - $gpuManufacturer = Get-GpuManufacturer - Write-StyledMessage Info "Rilevata GPU: $gpuManufacturer." - if ($gpuManufacturer -eq 'AMD') { - $amdInstallerUrl = $AppConfig.URLs.AMDInstaller - $amdInstallerPath = Join-Path $DesktopPath "AMD-Autodetect.exe" - if (-not (Download-FileWithProgress -Url $amdInstallerUrl -DestinationPath $amdInstallerPath -Description "AMD Auto-Detect Tool")) { - Write-StyledMessage Error "Impossibile scaricare l'installer AMD. Annullamento operazione." - return + $gpuAnalysis = VcardAnalizer + $gpuManufacturer = $gpuAnalysis.PrimaryManufacturer + Write-StyledMessage -Type 'Info' -Text "GPU rilevata: $gpuManufacturer." + $stableDownloadDone = $false + if ($gpuAnalysis.Matches.Count -gt 0) { + foreach ($match in $gpuAnalysis.Matches) { + if ([string]::IsNullOrWhiteSpace($match.DownloadUrl)) { continue } + $targetName = if (-not [string]::IsNullOrWhiteSpace($match.FileName)) { $match.FileName } else { "$($match.Key).exe" } + $targetPath = Join-Path $desktopPath $targetName + $displayName = if (-not [string]::IsNullOrWhiteSpace($match.DisplayName)) { $match.DisplayName } else { $match.Key } + if (Invoke-ToolkitDownload -Uri $match.DownloadUrl -OutputPath $targetPath -Description $displayName) { + Write-StyledMessage -Type 'Success' -Text "Driver stabile scaricato sul desktop: $displayName" + $stableDownloadDone = $true + } } } - elseif ($gpuManufacturer -eq 'NVIDIA') { - $nvidiaInstallerUrl = $AppConfig.URLs.NVCleanstall - $nvidiaInstallerPath = Join-Path $DesktopPath "NVCleanstall_1.19.0.exe" - if (-not (Download-FileWithProgress -Url $nvidiaInstallerUrl -DestinationPath $nvidiaInstallerPath -Description "NVCleanstall Tool")) { - Write-StyledMessage Error "Impossibile scaricare l'installer NVIDIA. Annullamento operazione." - return + if (-not $stableDownloadDone) { + Write-StyledMessage -Type 'Warning' -Text "Nessun driver stabile conosciuto trovato. Uso fallback autodetect." + switch ($gpuManufacturer) { + 'AMD' { + $amdPath = Join-Path $desktopPath "AMD-Autodetect.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.AMDInstaller -OutputPath $amdPath -Description "AMD Auto-Detect Tool")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare installer AMD. Annullamento." + return + } + } + 'NVIDIA' { + $nvidiaPath = Join-Path $desktopPath "NVCleanstall_1.19.0.exe" + if (-not (Invoke-ToolkitDownload -Uri $AppConfig.URLs.NVCleanstall -OutputPath $nvidiaPath -Description "NVCleanstall")) { + Write-StyledMessage -Type 'Error' -Text "❌ Impossibile scaricare NVCleanstall. Annullamento." + return + } + } + 'Intel' { + Write-StyledMessage -Type 'Info' -Text "GPU Intel: scarica driver manualmente da Intel se necessario." + } + default { + Write-StyledMessage -Type 'Warning' -Text "GPU non rilevata: solo DDU verrà posizionato sul Desktop." + } } } - elseif ($gpuManufacturer -eq 'Intel') { - Write-StyledMessage Info "Rilevata GPU Intel. Scarica manualmente i driver da Intel se necessario." - } - else { - Write-StyledMessage Warning "Produttore GPU non supportato o non rilevato. Verrà posizionato solo DDU sul desktop." - } - Write-StyledMessage Info "DDU e l'installer dei Driver (se rilevato) sono stati posizionati sul desktop." - $batchFilePath = Join-Path $DesktopPath "Switch to Normal Mode.bat" + $batchPath = Join-Path $desktopPath "Switch to Normal Mode.bat" try { - Set-Content -Path $batchFilePath -Value 'bcdedit /deletevalue {current} safeboot' -Encoding ASCII - Write-StyledMessage Info "File batch 'Switch to Normal Mode.bat' creato sul desktop per disabilitare la Modalità Provvisoria." + Set-Content -Path $batchPath -Value 'bcdedit /deletevalue {current} safeboot' -Encoding ASCII + Write-StyledMessage -Type 'Info' -Text "Batch 'Switch to Normal Mode.bat' creato sul Desktop." } catch { - Write-StyledMessage Warning "Impossibile creare il file batch: $($_.Exception.Message)." + Write-StyledMessage -Type 'Warning' -Text "Impossibile creare batch Safe Mode: $($_.Exception.Message)." } - Write-StyledMessage Error "ATTENZIONE: Il sistema sta per riavviarsi in modalità provvisoria." - Write-StyledMessage Info "Configurazione del sistema per l'avvio automatico in Modalità Provvisoria." + Write-StyledMessage -Type 'Error' -Text "ATTENZIONE: Il sistema si riavvierà in modalità provvisoria." + Write-StyledMessage -Type 'Info' -Text "In Safe Mode: esegui DDU per pulire i driver, poi reinstalla con l'installer sul Desktop. Infine usa il batch per tornare alla modalità normale." try { - $procParams = @{ - FilePath = 'bcdedit.exe' - ArgumentList = '/set {current} safeboot minimal' - Wait = $true - NoNewWindow = $true - ErrorAction = 'Stop' - } - Start-Process @procParams - Write-StyledMessage Success "Modalità Provvisoria configurata per il prossimo avvio." + $null = Invoke-WithSpinner -Activity "Configurazione Safe Mode (bcdedit)" -Command 'bcdedit.exe' ` + -Arguments '/set {current} safeboot minimal' -LogContextKey "Video-BCDEdit" + Write-StyledMessage -Type 'Success' -Text "Modalità provvisoria configurata per il prossimo avvio." + $needsReboot = $true } catch { - Write-StyledMessage Error "Errore durante la configurazione della Modalità Provvisoria tramite bcdedit: $($_.Exception.Message)." - Write-StyledMessage Warning "Il riavvio potrebbe non avvenire in Modalità Provvisoria. Procedere manualmente." - return - } - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio in modalità provvisoria soppresso (esecuzione concatenata)." - Write-StyledMessage -Type 'Warning' -Text "⚠️ DDU e installer driver sono sul Desktop. Al prossimo riavvio sarai in SAFE MODE." - } - else { - $shouldReboot = Start-InterruptibleCountdown -Seconds 30 -Message "Riavvio in modalità provvisoria in corso." - if ($shouldReboot) { - try { - Restart-Computer -Force - Write-StyledMessage Success "Comando di riavvio inviato." - } - catch { - Write-StyledMessage Error "Errore durante l'esecuzione del comando di riavvio: $($_.Exception.Message)." - } - } + Write-StyledMessage -Type 'Error' -Text "Errore configurazione Safe Mode: $($_.Exception.Message)." } } - Write-StyledMessage Info '🔧 Inizializzazione dello Script di Installazione Driver Video.' - Start-Sleep -Seconds 2 - Set-BlockWindowsUpdateDrivers - $choice = "" - do { - Write-Host "" - Write-StyledMessage Info 'Seleziona un''opzione:' - Write-Host " 1) Installa Driver Video." - Write-Host " 2) Reinstalla/Ripara Driver Video." - Write-Host " 0) Torna al menu principale." - Write-Host "" - $choice = Read-Host "La tua scelta" - Write-Host "" - switch ($choice.ToUpper()) { - "1" { Handle-InstallVideoDrivers } - "2" { Handle-ReinstallRepairVideoDrivers } - "0" { Write-StyledMessage Info 'Tornando al menu principale.' } - default { Write-StyledMessage Warning "Scelta non valida. Riprova." } - } - if ($choice.ToUpper() -ne "0") { - Write-Host "Premi un tasto per continuare." - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - Clear-Host - Show-Header -SubTitle "Video Driver Install Toolkit" + catch { + Write-StyledMessage -Type 'Error' -Text "Errore critico durante reinstallazione driver: $($_.Exception.Message)" + Write-ToolkitLog -Level ERROR -Message "Errore in VideoDriverReinstall" -Context @{ + Line = $_.InvocationInfo.ScriptLineNumber + Exception = $_.Exception.GetType().FullName + Stack = $_.ScriptStackTrace } - } while ($choice.ToUpper() -ne "0") + } + finally { + Write-StyledMessage -Type 'Success' -Text "🎯 Video Driver Reinstall terminato." + Write-ToolkitLog -Level INFO -Message "VideoDriverReinstall sessione terminata." + } + if ($needsReboot) { + Invoke-ToolkitReboot -Message "Riavvio in Safe Mode per DDU" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot + } } function GamingToolkit { [CmdletBinding()] param( - [Parameter(Mandatory = $false)] + [Parameter()] + [ValidateRange(0, 300)] [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "GamingToolkit" - Show-Header -SubTitle "Gaming Toolkit" - $Host.UI.RawUI.WindowTitle = "Gaming Toolkit By MagnetarMan" + Start-ToolkitSession -ToolName "GamingToolkit" -SubTitle "Gaming Toolkit" $timeout = 3600 function Test-WingetPackageAvailable([string]$PackageId) { try { @@ -4059,17 +4185,7 @@ function GamingToolkit { $outFile = "$env:TEMP\winget_$PackageId.log" $errFile = "$env:TEMP\winget_err_$PackageId.log" try { - $result = Invoke-WithSpinner -Activity "Installazione $DisplayName" -Process -Action { - $procParams = @{ - FilePath = 'winget' - ArgumentList = @('install', '--id', $PackageId, '--silent', '--disable-interactivity', '--accept-package-agreements', '--accept-source-agreements') - PassThru = $true - NoNewWindow = $true - RedirectStandardOutput = $outFile - RedirectStandardError = $errFile - } - Start-Process @procParams - } -TimeoutSeconds $timeout -UpdateInterval 700 + $result = Invoke-WithSpinner -Activity "Installazione $DisplayName" -Command 'winget' -Arguments @('install', '--id', $PackageId, '--silent', '--disable-interactivity', '--accept-package-agreements', '--accept-source-agreements') -TimeoutSeconds $timeout -LogContextKey "Gaming-Install-$PackageId" $exitCode = if ($result -is [hashtable] -and $result.Contains('ExitCode')) { $result.ExitCode } else { -1 } $successCodes = @(0, 1638, 3010, -1978335189) if ($exitCode -in $successCodes) { @@ -4082,50 +4198,48 @@ function GamingToolkit { } } catch { - Write-Host "`r$(' ' * 120)" -NoNewline - Write-Host "`r" -NoNewline + Clear-ProgressLine + Clear-ProgressLine Write-StyledMessage -Type 'Error' -Text "Eccezione $DisplayName : $($_.Exception.Message)." return @{ Success = $false } } finally { - Remove-Item $outFile, $errFile -ErrorAction SilentlyContinue + Remove-ItemSafely -Path $outFile + Remove-ItemSafely -Path $errFile } } - $Host.UI.RawUI.WindowTitle = "Gaming Toolkit by MagnetarMan" Invoke-WithSpinner -Activity "Preparazione" -Timer -Action { Start-Sleep 5 } -TimeoutSeconds 5 Show-Header -SubTitle "Gaming Toolkit" - Write-StyledMessage Info '🔍 Verifica disponibilità Winget.' + Write-StyledMessage -Type 'Info' -Text '🔍 Verifica disponibilità Winget.' Update-EnvironmentPath if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { - Write-StyledMessage Warning '⚠️ Winget non disponibile. Avvio ripristino automatico...' + Write-StyledMessage -Type 'Warning' -Text '⚠️ Winget non disponibile. Avvio ripristino automatico...' $resetOk = Reset-Winget Update-EnvironmentPath if (-not $resetOk -or -not (Get-Command winget -ErrorAction SilentlyContinue)) { - Write-StyledMessage Error '❌ Ripristino Winget fallito. Impossibile procedere con Gaming Toolkit.' + Write-StyledMessage -Type 'Error' -Text '❌ Ripristino Winget fallito. Impossibile procedere con Gaming Toolkit.' Write-StyledMessage -Type 'Info' -Text 'Premi un tasto per continuare.' $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') return } } - Write-StyledMessage Success '✅ Winget disponibile.' - Write-StyledMessage Info '🔄 Aggiornamento sorgenti Winget.' + Write-StyledMessage -Type 'Success' -Text '✅ Winget disponibile.' + Write-StyledMessage -Type 'Info' -Text '🔄 Aggiornamento sorgenti Winget.' try { - winget source update | Out-Null - Write-StyledMessage Success 'Sorgenti aggiornate.' + winget source update *>$null + Write-StyledMessage -Type 'Success' -Text 'Sorgenti aggiornate.' } catch { - Write-StyledMessage Warning "Errore aggiornamento sorgenti: $($_.Exception.Message)." + Write-StyledMessage -Type 'Warning' -Text "Errore aggiornamento sorgenti: $($_.Exception.Message)." } - Write-Host '' - Write-StyledMessage Info '🔧 Abilitazione NetFramework.' + Write-StyledMessage -Type 'Info' -Text '🔧 Abilitazione NetFramework.' try { - Enable-WindowsOptionalFeature -Online -FeatureName NetFx4-AdvSrvs, NetFx3 -NoRestart -All -ErrorAction Stop | Out-Null - Write-StyledMessage Success 'NetFramework abilitato.' + Enable-WindowsOptionalFeature -Online -FeatureName NetFx4-AdvSrvs, NetFx3 -NoRestart -All -ErrorAction Stop *>$null + Write-StyledMessage -Type 'Success' -Text 'NetFramework abilitato.' } catch { - Write-StyledMessage Error "Errore durante abilitazione NetFramework: $($_.Exception.Message)." + Write-StyledMessage -Type 'Error' -Text "Errore durante abilitazione NetFramework: $($_.Exception.Message)." } - Write-Host '' $runtimes = @( "Microsoft.DotNet.DesktopRuntime.3_1", "Microsoft.DotNet.DesktopRuntime.5", @@ -4144,34 +4258,26 @@ function GamingToolkit { "Microsoft.VCRedist.2015+.x64", "Microsoft.VCRedist.2015+.x86" ) - Write-StyledMessage Info '🔥 Installazione runtime .NET e VCRedist.' + Write-StyledMessage -Type 'Info' -Text '🔥 Installazione runtime .NET e VCRedist.' for ($runtimeIndex = 0; $runtimeIndex -lt $runtimes.Count; $runtimeIndex++) { - Invoke-WingetInstallWithProgress $runtimes[$runtimeIndex] $runtimes[$runtimeIndex] ($runtimeIndex + 1) $runtimes.Count | Out-Null - Write-Host '' + Invoke-WingetInstallWithProgress $runtimes[$runtimeIndex] $runtimes[$runtimeIndex] ($runtimeIndex + 1) $runtimes.Count *>$null } - Write-StyledMessage Success 'Runtime completati.' - Write-Host '' - Write-StyledMessage Info '🎮 Installazione DirectX.' - $dxDir = "$env:LOCALAPPDATA\WinToolkit\Directx" + Write-StyledMessage -Type 'Success' -Text 'Runtime completati.' + Write-StyledMessage -Type 'Info' -Text '🎮 Installazione DirectX.' + $dxDir = Join-Path $AppConfig.Paths.LocalAppData "WinToolkit\Directx" $dxPath = "$dxDir\dxwebsetup.exe" - if (-not (Test-Path $dxDir)) { New-Item -Path $dxDir -ItemType Directory -Force | Out-Null } + if (-not (Test-Path $dxDir)) { New-Item -Path $dxDir -ItemType Directory -Force *>$null } try { Invoke-WebRequest -Uri $AppConfig.URLs.DirectXWebSetup -OutFile $dxPath -ErrorAction Stop - Write-StyledMessage Success 'DirectX scaricato.' - $result = Invoke-WithSpinner -Activity "Installazione DirectX" -Process -Action { - $procParams = @{ - FilePath = $dxPath - PassThru = $true - } - Start-Process @procParams - } -TimeoutSeconds $timeout -UpdateInterval 700 - Write-Host "`r$(' ' * 120)" -NoNewline - Write-Host "`r" -NoNewline + Write-StyledMessage -Type 'Success' -Text 'DirectX scaricato.' + $result = Invoke-WithSpinner -Activity "Installazione DirectX" -Command $dxPath -TimeoutSeconds $timeout -LogContextKey "Gaming-DirectX" + Clear-ProgressLine + Clear-ProgressLine if ($null -eq $result) { - Write-StyledMessage Error "DirectX: processo non avviato correttamente." + Write-StyledMessage -Type 'Error' -Text "DirectX: processo non avviato correttamente." } elseif ($result -is [hashtable] -and $result.Contains('TimedOut') -and $result.TimedOut) { - Write-StyledMessage Warning "Timeout DirectX." + Write-StyledMessage -Type 'Warning' -Text "Timeout DirectX." } else { $exitCode = if ($result -is [hashtable] -and $result.Contains('ExitCode')) { $result.ExitCode } else { -1 } @@ -4180,24 +4286,21 @@ function GamingToolkit { } } catch { - Write-Host "`r$(' ' * 120)" -NoNewline - Write-Host "`r" -NoNewline - Write-StyledMessage Error "Errore durante installazione DirectX: $($_.Exception.Message)" + Clear-ProgressLine + Clear-ProgressLine + Write-StyledMessage -Type 'Error' -Text "Errore durante installazione DirectX: $($_.Exception.Message)" } - Write-Host '' $gameClients = @( "Amazon.Games", "GOG.Galaxy", "EpicGames.EpicGamesLauncher", "ElectronicArts.EADesktop", "Playnite.Playnite", "Valve.Steam", "Ubisoft.Connect" ) - Write-StyledMessage Info '🎮 Installazione client di gioco.' + Write-StyledMessage -Type 'Info' -Text '🎮 Installazione client di gioco.' for ($clientIndex = 0; $clientIndex -lt $gameClients.Count; $clientIndex++) { - Invoke-WingetInstallWithProgress $gameClients[$clientIndex] $gameClients[$clientIndex] ($clientIndex + 1) $gameClients.Count | Out-Null - Write-Host '' + Invoke-WingetInstallWithProgress $gameClients[$clientIndex] $gameClients[$clientIndex] ($clientIndex + 1) $gameClients.Count *>$null } - Write-StyledMessage Success 'Client installati.' - Write-Host '' - Write-StyledMessage Info '🎮 Reinstallazione Xbox Game Bar & App.' + Write-StyledMessage -Type 'Success' -Text 'Client installati.' + Write-StyledMessage -Type 'Info' -Text '🎮 Reinstallazione Xbox Game Bar & App.' $xboxPackages = @("9NZKPSTSNW4P", "9MV0B5HZVK9Z") foreach ($pkg in $xboxPackages) { Write-StyledMessage -Type 'Info' -Text "Reinstallazione: $pkg." @@ -4228,34 +4331,24 @@ function GamingToolkit { Write-StyledMessage -Type 'Error' -Text "Errore $pkg : $($_.Exception.Message)." } finally { - Remove-Item $outFile, $errFile -ErrorAction SilentlyContinue + Remove-ItemSafely -Path $outFile + Remove-ItemSafely -Path $errFile } - Write-Host '' } - Write-StyledMessage Success 'Xbox reinstallati.' - Write-Host '' - Write-StyledMessage Info '🎮 Installazione Battle.net.' + Write-StyledMessage -Type 'Success' -Text 'Xbox reinstallati.' + Write-StyledMessage -Type 'Info' -Text '🎮 Installazione Battle.net.' $bnPath = "$env:TEMP\Battle.net-Setup.exe" try { Invoke-WebRequest -Uri $AppConfig.URLs.BattleNetInstaller -OutFile $bnPath -ErrorAction Stop - Write-StyledMessage Success 'Battle.net scaricato.' - $result = Invoke-WithSpinner -Activity "Installazione Battle.net" -Process -Action { - $procParams = @{ - FilePath = $bnPath - ArgumentList = '--quiet' - PassThru = $true - Verb = 'RunAs' - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds $timeout -UpdateInterval 500 - Write-Host "`r$(' ' * 120)" -NoNewline - Write-Host "`r" -NoNewline + Write-StyledMessage -Type 'Success' -Text 'Battle.net scaricato.' + $result = Invoke-WithSpinner -Activity "Installazione Battle.net" -Command $bnPath -Arguments '--quiet' -TimeoutSeconds $timeout -LogContextKey "Gaming-BattleNet" + Clear-ProgressLine + Clear-ProgressLine if ($null -eq $result) { - Write-StyledMessage Error "Battle.net: processo non avviato correttamente." + Write-StyledMessage -Type 'Error' -Text "Battle.net: processo non avviato correttamente." } elseif ($result -is [hashtable] -and $result.Contains('TimedOut') -and $result.TimedOut) { - Write-StyledMessage Warning "Timeout Battle.net." + Write-StyledMessage -Type 'Warning' -Text "Timeout Battle.net." } else { $exitCode = if ($result -is [hashtable] -and $result.Contains('ExitCode')) { $result.ExitCode } else { -1 } @@ -4265,202 +4358,103 @@ function GamingToolkit { $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } catch { - Write-Host "`r$(' ' * 120)" -NoNewline - Write-Host "`r" -NoNewline - Write-StyledMessage Error "Errore durante installazione Battle.net: $($_.Exception.Message)" + Clear-ProgressLine + Clear-ProgressLine + Write-StyledMessage -Type 'Error' -Text "Errore durante installazione Battle.net: $($_.Exception.Message)" Write-StyledMessage -Type 'Info' -Text 'Premi un tasto per continuare.' $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } - Write-Host '' - Write-StyledMessage Info '🧹 Pulizia avvio automatico.' + Write-StyledMessage -Type 'Info' -Text '🧹 Pulizia avvio automatico.' $runKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' @('Steam', 'Battle.net', 'GOG Galaxy', 'GogGalaxy', 'GalaxyClient') | ForEach-Object { if (Get-ItemProperty -Path $runKey -Name $_ -ErrorAction SilentlyContinue) { Remove-ItemProperty -Path $runKey -Name $_ -ErrorAction SilentlyContinue - Write-StyledMessage Success "Rimosso: $_" + Write-StyledMessage -Type 'Success' -Text "Rimosso: $_" } } - $startupPath = [Environment]::GetFolderPath('Startup') + $startupPath = $AppConfig.Paths.Startup @('Steam.lnk', 'Battle.net.lnk', 'GOG Galaxy.lnk') | ForEach-Object { $path = Join-Path $startupPath $_ - if (Test-Path $path) { - Remove-Item $path -Force -ErrorAction SilentlyContinue - Write-StyledMessage Success "Rimosso: $_" + Remove-ItemSafely -Path $path + if (-not (Test-Path $path)) { + Write-StyledMessage -Type 'Success' -Text "Rimosso: $_" } } - Write-StyledMessage Success 'Pulizia completata.' - Write-Host '' - Write-StyledMessage Info '⚡ Configurazione profilo energetico.' + Write-StyledMessage -Type 'Success' -Text 'Pulizia completata.' + Write-StyledMessage -Type 'Info' -Text '⚡ Configurazione profilo energetico.' $ultimateGUID = "e9a42b02-d5df-448d-aa00-03f14749eb61" $planName = "WinToolkit Gaming Performance" $guid = $null $existingPlan = powercfg -list | Select-String -Pattern $planName -ErrorAction SilentlyContinue if ($existingPlan) { $guid = ($existingPlan.Line -split '\s+')[3] - Write-StyledMessage Info "Piano esistente trovato." + Write-StyledMessage -Type 'Info' -Text "Piano esistente trovato." } else { try { $output = powercfg /duplicatescheme $ultimateGUID | Out-String if ($output -match "\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b") { $guid = $matches[0] - powercfg /changename $guid $planName "Ottimizzato per Gaming dal WinToolkit" | Out-Null - Write-StyledMessage Success "Piano creato." + powercfg /changename $guid $planName "Ottimizzato per Gaming dal WinToolkit" *>$null + Write-StyledMessage -Type 'Success' -Text "Piano creato." } else { - Write-StyledMessage Error "Errore creazione piano." + Write-StyledMessage -Type 'Error' -Text "Errore creazione piano." } } catch { - Write-StyledMessage Error "Errore durante duplicazione piano energetico: $($_.Exception.Message)" + Write-StyledMessage -Type 'Error' -Text "Errore durante duplicazione piano energetico: $($_.Exception.Message)" } } if ($guid) { try { - powercfg -setactive $guid | Out-Null - Write-StyledMessage Success "Piano attivato." + powercfg -setactive $guid *>$null + Write-StyledMessage -Type 'Success' -Text "Piano attivato." } catch { - Write-StyledMessage Error "Errore durante attivazione piano energetico: $($_.Exception.Message)." + Write-StyledMessage -Type 'Error' -Text "Errore durante attivazione piano energetico: $($_.Exception.Message)." } } else { - Write-StyledMessage Error "Impossibile attivare piano." + Write-StyledMessage -Type 'Error' -Text "Impossibile attivare piano." } - Write-Host '' - Write-StyledMessage Info '🔕 Attivazione Non disturbare.' + Write-StyledMessage -Type 'Info' -Text '🔕 Attivazione Non disturbare.' try { Set-ItemProperty -Path $AppConfig.Registry.FocusAssist -Name "NOC_GLOBAL_SETTING_TOASTS_ENABLED" -Value 0 -Force - Write-StyledMessage Success 'Non disturbare attivo.' + Write-StyledMessage -Type 'Success' -Text 'Non disturbare attivo.' } catch { - Write-StyledMessage Error "Errore durante configurazione Focus Assist: $($_.Exception.Message)" + Write-StyledMessage -Type 'Error' -Text "Errore durante configurazione Focus Assist: $($_.Exception.Message)" } - Write-Host '' Write-StyledMessage -Type 'Info' -Text ('─' * 60) Write-StyledMessage -Type 'Success' -Text 'Gaming Toolkit completato!' Write-StyledMessage -Type 'Success' -Text 'Sistema ottimizzato per il gaming.' Write-StyledMessage -Type 'Info' -Text ('─' * 60) - Write-Host '' - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - Write-StyledMessage -Type 'Info' -Text "🚫 Riavvio individuale soppresso. Verrà gestito un riavvio finale." - } - else { - if (Start-InterruptibleCountdown -Seconds $CountdownSeconds -Message "Riavvio necessario") { - Write-StyledMessage Info '🔄 Riavvio.' - Restart-Computer -Force - } - else { - Write-StyledMessage Warning 'Riavvia manualmente per applicare tutte le modifiche.' - Write-StyledMessage -Type 'Info' -Text 'Premi un tasto per continuare.' - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - } - } -} -function DisableBitlocker { - [CmdletBinding()] - param( - [Parameter(Mandatory = $false)] - [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] - [switch]$SuppressIndividualReboot - ) - Start-ToolkitLog -ToolName "DisableBitlocker" - Show-Header -SubTitle "Disable BitLocker Toolkit" - $Host.UI.RawUI.WindowTitle = "Disable BitLocker Toolkit By MagnetarMan" - $regPath = $AppConfig.Registry.BitLocker - $timeout = 3600 - function Test-BitLockerStatus { - param([string]$DriveLetter = "C:") - try { - $status = manage-bde -status $DriveLetter - return $status - } - catch { - Write-StyledMessage -Type 'Warning' -Text "Impossibile verificare lo stato BitLocker: $($_.Exception.Message)" - return $null - } - } - try { - Write-StyledMessage -Type 'Info' -Text "🚀 Inizializzazione decrittazione drive C:." - $result = Invoke-WithSpinner -Activity "Disattivazione BitLocker" -Process -Action { - $procParams = @{ - FilePath = 'manage-bde.exe' - ArgumentList = @('-off', 'C:') - PassThru = $true - WindowStyle = 'Hidden' - } - Start-Process @procParams - } -TimeoutSeconds $timeout - if ($result.ExitCode -eq 0) { - Write-StyledMessage -Type 'Success' -Text "✅ Decrittazione avviata/completata con successo." - Start-Sleep -Seconds 2 - $status = Test-BitLockerStatus -DriveLetter "C:" - if ($status -match "Decryption in progress" -or $status -match "Decriptazione in corso.") { - Write-StyledMessage -Type 'Info' -Text "⏳ Decrittazione in corso in background." - } - } - else { - Write-StyledMessage -Type 'Warning' -Text "⚠️ Codice uscita manage-bde: $($result.ExitCode). BitLocker potrebbe essere già disattivo o in errore." - } - Write-StyledMessage -Type 'Info' -Text "⚙️ Disabilitazione crittografia automatica nel registro." - if (-not (Test-Path $regPath)) { - New-Item -Path $regPath -Force *>$null - } - Set-ItemProperty -Path $regPath -Name "PreventDeviceEncryption" -Type DWord -Value 1 -Force - Write-StyledMessage -Type 'Success' -Text "🎉 Configurazione completata." - } - catch { - Write-StyledMessage -Type 'Error' -Text "❌ Errore critico in DisableBitlocker: $($_.Exception.Message)" - Write-ToolkitLog -Level ERROR -Message "Errore critico in DisableBitlocker" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace - } - } - finally { - Write-StyledMessage -Type 'Info' -Text "♻️ Pulizia risorse Completata." - if ($SuppressIndividualReboot) { - $Global:NeedsFinalReboot = $true - } - else { - if (Start-InterruptibleCountdown -Seconds $CountdownSeconds -Message "Riavvio in") { - Restart-Computer -Force - } - } - Write-ToolkitLog -Level INFO -Message "DisableBitlocker sessione terminata." - } + Invoke-ToolkitReboot -Message "Riavvio necessario" -Seconds $CountdownSeconds -SuppressIndividualReboot:$SuppressIndividualReboot } function WinExportLog { [CmdletBinding()] param( - [Parameter(Mandatory = $false)] [int]$CountdownSeconds = 30, - [Parameter(Mandatory = $false)] [switch]$SuppressIndividualReboot ) - Start-ToolkitLog -ToolName "WinExportLog" - Show-Header -SubTitle "Esporta Log Diagnostici" - $Host.UI.RawUI.WindowTitle = "Log Export By MagnetarMan" + Start-ToolkitSession -ToolName "WinExportLog" -SubTitle "Esporta Log Diagnostici" $logSourcePath = $AppConfig.Paths.Logs - $desktopPath = $AppConfig.Paths.Desktop - $timestamp = (Get-Date -Format "yyyyMMdd_HHmmss") - $zipFileName = "WinToolkit_Logs_$timestamp.zip" - $zipFilePath = Join-Path $desktopPath $zipFileName + $desktopPath = $AppConfig.Paths.Desktop + $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" + $zipFileName = "WinToolkit_Logs_$timestamp.zip" + $zipFilePath = Join-Path $desktopPath $zipFileName + $tempFolder = Join-Path $AppConfig.Paths.TempFolder "WinToolkit_Logs_Temp_$timestamp" try { - Write-StyledMessage Info "📂 Verifica presenza cartella log." + Write-StyledMessage -Type 'Info' -Text "📂 Verifica presenza cartella log." if (-not (Test-Path $logSourcePath -PathType Container)) { - Write-StyledMessage Warning "La cartella dei log '$logSourcePath' non è stata trovata. Impossibile esportare." + Write-StyledMessage -Type 'Warning' -Text "La cartella dei log '$logSourcePath' non è stata trovata. Impossibile esportare." return } - Write-StyledMessage Info "🗜️ Compressione dei log in corso. Potrebbe essere ignorato qualche file in uso." - $tempFolder = Join-Path $AppConfig.Paths.TempFolder "WinToolkit_Logs_Temp_$timestamp" - if (Test-Path $tempFolder) { - Remove-Item $tempFolder -Recurse -Force -ErrorAction SilentlyContinue - } - New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null - $filesCopied = 0 + Write-StyledMessage -Type 'Info' -Text "🗜️ Compressione dei log in corso. Potrebbe essere ignorato qualche file in uso." + Remove-ItemSafely -Path $tempFolder -Recurse + New-Item -ItemType Directory -Path $tempFolder -Force *>$null + $filesCopied = 0 $filesSkipped = 0 try { Get-ChildItem -Path $logSourcePath -File | ForEach-Object { @@ -4475,177 +4469,52 @@ function WinExportLog { } } catch { - Write-StyledMessage Warning "Errore durante la copia dei file: $($_.Exception.Message)." + Write-StyledMessage -Type 'Warning' -Text "Errore durante la copia dei file: $($_.Exception.Message)." } if ($filesCopied -gt 0) { Compress-Archive -Path "$tempFolder\*" -DestinationPath $zipFilePath -Force -ErrorAction Stop if (Test-Path $zipFilePath) { - Write-StyledMessage Success "Log compressi con successo! File salvato: '$zipFileName' sul Desktop." + Write-StyledMessage -Type 'Success' -Text "Log compressi con successo! File salvato: '$zipFileName' sul Desktop." if ($filesSkipped -gt 0) { - Write-StyledMessage Info "⚠️ Attenzione: $filesSkipped file sono stati ignorati perché in uso o non accessibili." + Write-StyledMessage -Type 'Info' -Text "⚠️ $filesSkipped file ignorati perché in uso o non accessibili." } - Write-StyledMessage Info "📩 Per favore, invia il file ZIP '$zipFileName' (lo trovi sul tuo Desktop) via Telegram [https://t.me/MagnetarMan] o email [me@magnetarman.com] per aiutarmi nella diagnostica." + Write-StyledMessage -Type 'Info' -Text "📩 Invia '$zipFileName' (Desktop) via Telegram [https://t.me/MagnetarMan] o email [me@magnetarman.com] per la diagnostica." } else { - Write-StyledMessage Error "Errore sconosciuto: il file ZIP non è stato creato." + Write-StyledMessage -Type 'Error' -Text "Errore sconosciuto: il file ZIP non è stato creato." } } else { - Write-StyledMessage Error "Nessun file log è stato copiato. Verifica i permessi e che i file esistano." - } - if (Test-Path $tempFolder) { - Remove-Item $tempFolder -Recurse -Force -ErrorAction SilentlyContinue + Write-StyledMessage -Type 'Error' -Text "Nessun file log copiato. Verifica i permessi e che i file esistano." } } catch { - Write-StyledMessage Error "Errore critico durante la compressione dei log: $($_.Exception.Message)." - Write-ToolkitLog -Level ERROR -Message "Errore critico in WinExportLog" -Context @{ - Line = $_.InvocationInfo.ScriptLineNumber - Exception = $_.Exception.GetType().FullName - Stack = $_.ScriptStackTrace - } - $tempFolder = Join-Path $env:TEMP "WinToolkit_Logs_Temp_$timestamp" - if (Test-Path $tempFolder) { - Remove-Item $tempFolder -Recurse -Force -ErrorAction SilentlyContinue - } - } -} -function Read-ValidatedChoice { - [CmdletBinding()] - param( - [string]$Prompt = 'Selezione', - [int]$Min = 1, - [int]$Max = 99, - [switch]$AllowZero, - [string]$RawInput - ) - if ([string]::IsNullOrEmpty($RawInput)) { - $rawInput = Read-Host $Prompt - } - else { - $rawInput = $RawInput - } - if ($null -eq $rawInput) { return @() } - if ($AllowZero -and $rawInput.Trim() -eq '0') { - Write-ToolkitLog -Level 'INFO' -Message 'Utente ha selezionato: 0 (uscita/annulla)' -Context @{ Input = '0' } - return @(0) - } - $tokens = $rawInput -split '[\s,]+' | Where-Object { $_ -match '^\d+$' } - $valid = @() - $invalid = @() - foreach ($token in $tokens) { - $num = [int]$token - if ($num -ge $Min -and $num -le $Max) { - if ($valid -notcontains $num) { $valid += $num } - } - else { - $invalid += $num - } - } - if ($invalid.Count -gt 0) { - Write-StyledMessage -Type Warning -Text "⚠️ Valori fuori range ignorati: $($invalid -join ', ') (range valido: $Min–$Max)" - } - Write-ToolkitLog -Level 'INFO' -Message 'Input utente validato' -Context @{ - RawInput = $rawInput - Valid = ($valid -join ',') - Invalid = ($invalid -join ',') - } - return $valid -} -function Get-UserConfirmation { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string]$Prompt, - [ValidateSet('Y', 'N')] - [string]$Default = 'N', - [ValidateSet('Info', 'Warning')] - [string]$Severity = 'Info' - ) - Write-StyledMessage -Type Warning -Text "⚠️ [DEPRECATED] Get-UserConfirmation sarà rimossa. Non richiederà più conferme." - $yesLabel = if ($Default -eq 'Y') { '[Y]' } else { 'y' } - $noLabel = if ($Default -eq 'N') { '[N]' } else { 'n' } - $fullPrompt = "$Prompt ($yesLabel/$noLabel)" - Write-StyledMessage -Type $Severity -Text $fullPrompt - $answer = Read-Host '' - if ([string]::IsNullOrWhiteSpace($answer)) { $answer = $Default } - $confirmed = $answer -match '^[Yy]' - Write-ToolkitLog -Level 'INFO' -Message 'Conferma utente' -Context @{ - Prompt = $Prompt - Default = $Default - Answer = $answer - Confirmed = $confirmed - } - return $confirmed -} -function Show-ConsoleTable { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [object[]]$Rows, - [Parameter(Mandatory = $true)] - [hashtable[]]$Columns, - [string]$Title = '' - ) - $widths = @{} - foreach ($col in $Columns) { - $widths[$col.Key] = $col.Header.Length - } - foreach ($row in $Rows) { - foreach ($col in $Columns) { - $val = if ($row -is [hashtable]) { "$($row[$col.Key])" } else { "$($row.$($col.Key))" } - if ($val.Length -gt $widths[$col.Key]) { $widths[$col.Key] = $val.Length } - } - } - $sep = '+' + (($Columns | ForEach-Object { '-' * ($widths[$_.Key] + 2) }) -join '+') + '+' - if ($Title) { - $totalWidth = $sep.Length - $paddedTitle = " $Title " - $pad = [Math]::Max(0, [Math]::Floor(($totalWidth - $paddedTitle.Length) / 2)) - Write-Host ('=' * $totalWidth) -ForegroundColor Cyan - Write-Host ((' ' * $pad) + $paddedTitle) -ForegroundColor Cyan - Write-Host ('=' * $totalWidth) -ForegroundColor Cyan - } - Write-Host $sep -ForegroundColor DarkGray - $headerLine = '|' - foreach ($col in $Columns) { - $headerLine += ' ' + $col.Header.PadRight($widths[$col.Key]) + ' |' + Write-ToolkitError -Record $_ -ToolName "WinExportLog" -Message "Errore durante la compressione dei log" } - Write-Host $headerLine -ForegroundColor Cyan - Write-Host $sep -ForegroundColor DarkGray - foreach ($row in $Rows) { - $line = '|' - foreach ($col in $Columns) { - $val = if ($row -is [hashtable]) { "$($row[$col.Key])" } else { "$($row.$($col.Key))" } - $cell = ' ' + $val.PadRight($widths[$col.Key]) + ' |' - $color = if ($col.Color) { $col.Color } else { 'White' } - $line += $cell - } - $rowColor = 'White' - $statusKey = ($Columns | Where-Object { $_.Key -eq 'Status' -or $_.Key -eq 'Stato' } | Select-Object -First 1)?.Key - if ($statusKey) { - $statusVal = if ($row -is [hashtable]) { "$($row[$statusKey])" } else { "$($row.$statusKey)" } - if ($statusVal -match '✅|OK|Successo|Completato') { $rowColor = 'Green' } - elseif ($statusVal -match '⚠️|Warning|Parziale') { $rowColor = 'Yellow' } - elseif ($statusVal -match '❌|Errore|Fallito') { $rowColor = 'Red' } - } - Write-Host $line -ForegroundColor $rowColor + finally { + Remove-ItemSafely -Path $tempFolder -Recurse + Write-ToolkitLog -Level INFO -Message "WinExportLog sessione terminata." } - Write-Host $sep -ForegroundColor DarkGray } $menuStructure = @( - @{ 'Name' = 'Windows & Office'; 'Icon' = '🔧'; 'Scripts' = @( + @{ 'Name' = 'Windows'; 'Icon' = '🔧'; 'Scripts' = @( [pscustomobject]@{Name = 'WinRepairToolkit'; Description = 'Riparazione Windows'; Action = 'RunFunction' }, [pscustomobject]@{Name = 'WinUpdateReset'; Description = 'Reset Windows Update'; Action = 'RunFunction' }, [pscustomobject]@{Name = 'WinReinstallStore'; Description = 'Winget/WinStore Reset'; Action = 'RunFunction' }, [pscustomobject]@{Name = 'WinBackupDriver'; Description = 'Backup Driver PC'; Action = 'RunFunction' }, [pscustomobject]@{Name = 'WinCleaner'; Description = 'Pulizia File Temporanei'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'DisableBitlocker'; Description = 'Disabilita Bitlocker'; Action = 'RunFunction' }, - [pscustomobject]@{Name = 'OfficeToolkit'; Description = 'Office Toolkit'; Action = 'RunFunction' } + [pscustomobject]@{Name = 'DisableBitlocker'; Description = 'Disabilita Bitlocker'; Action = 'RunFunction' } + ) + }, + @{ 'Name' = 'Office'; 'Icon' = '🏢'; 'Scripts' = @( + [pscustomobject]@{Name = 'Install-Office'; Description = 'Installa Office Basic'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'Repair-Office'; Description = 'Ripara Office'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'Uninstall-Office'; Description = 'Rimuovi Office'; Action = 'RunFunction' } ) }, @{ 'Name' = 'Driver & Gaming'; 'Icon' = '🎮'; 'Scripts' = @( - [pscustomobject]@{Name = 'VideoDriverInstall'; Description = 'Driver Video Toolkit'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'AutoVideoDriverInstall'; Description = 'Auto Install Driver Video [Nvidia-AMD]'; Action = 'RunFunction' }, + [pscustomobject]@{Name = 'VideoDriverReinstall'; Description = 'Reinstalla Driver Video'; Action = 'RunFunction' }, [pscustomobject]@{Name = 'GamingToolkit'; Description = 'Gaming Toolkit'; Action = 'RunFunction' } ) }, @@ -4654,15 +4523,18 @@ $menuStructure = @( ) } ) -WinOSCheck +if (-not $ImportOnly) { + Initialize-ToolkitPaths + WinOSCheck + Test-WindowsUpdateStatus +} if (-not $ImportOnly -and -not $Global:GuiSessionActive) { Write-Host "" Write-StyledMessage -Type 'Info' -Text '💎 WinToolkit avviato in modalità interattiva' Write-Host "" while ($true) { Show-Header -SubTitle "Menu Principale" - $width = $Host.UI.RawUI.BufferSize.Width - Write-Host ('*' * 50) -ForegroundColor Red + $width = try { $Host.UI.RawUI.BufferSize.Width } catch { 80 } Write-Host '' Write-Host "==== 💻 INFORMAZIONI DI SISTEMA 💻 ====" -ForegroundColor Cyan Write-Host '' @@ -4672,28 +4544,21 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { Write-Host "💻 Edizione: $editionIcon $($si.ProductName)" -ForegroundColor White Write-Host "🆔 Versione: " -NoNewline -ForegroundColor White Write-Host "Ver. $($si.DisplayVersion) (Build $($si.BuildNumber))" -ForegroundColor Green - Write-Host "🔑 Architettura: $($si.Architecture)" -ForegroundColor White - Write-Host "🔧 Nome PC: $($si.ComputerName)" -ForegroundColor White - Write-Host "🧠 RAM: $($si.TotalRAM) GB" -ForegroundColor White + Write-Host "🔑 Architettura: $($si.Architecture)" -ForegroundColor White + Write-Host "🔧 Nome PC: $($si.ComputerName)" -ForegroundColor White + Write-Host "🧠 RAM: $($si.TotalRAM) GB" -ForegroundColor White Write-Host "💾 Disco: " -NoNewline -ForegroundColor White $diskFreeGB = $si.FreeDisk $displayString = "$($si.FreePercentage)% Libero ($($diskFreeGB) GB)" - $diskColor = "Green" - if ($diskFreeGB -lt 50) { - $diskColor = "Red" - } - elseif ($diskFreeGB -ge 50 -and $diskFreeGB -le 80) { - $diskColor = "Yellow" - } + $diskColor = if ($diskFreeGB -lt 50) { "Red" } elseif ($diskFreeGB -le 80) { "Yellow" } else { "Green" } Write-Host $displayString -ForegroundColor $diskColor -NoNewline Write-Host "" $blStatus = Get-BitlockerStatus - $blColor = 'Red' - if ($blStatus -match 'Disattivato|Non configurato|Off') { $blColor = 'Green' } + $blColor = if ($blStatus -match 'Disattivato|Non configurato|Off') { 'Green' } else { 'Red' } Write-Host "🔒 Stato Bitlocker: " -NoNewline -ForegroundColor White Write-Host "$blStatus" -ForegroundColor $blColor - Write-Host ('*' * 50) -ForegroundColor Red } + Write-Host ('*' * 50) -ForegroundColor Red Write-Host "" $allScripts = @(); $idx = 1 foreach ($cat in $menuStructure) { @@ -4743,12 +4608,8 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { Write-StyledMessage -Type 'Progress' -Text "▶️ Avvio: $($scriptToRun.Description)" Write-Host '' try { - if ($isMultiScript) { - & ([scriptblock]::Create("$($scriptToRun.Name) -SuppressIndividualReboot")) - } - else { - & $ExecutionContext.InvokeCommand.GetCommand($scriptToRun.Name, 'Function') - } + if ($isMultiScript) { & ([scriptblock]::Create("$($scriptToRun.Name) -SuppressIndividualReboot")) } + else { & $ExecutionContext.InvokeCommand.GetCommand($scriptToRun.Name, 'Function') } $Global:ExecutionLog += @{ Name = $scriptToRun.Description; Success = $true } } catch { @@ -4760,18 +4621,13 @@ if (-not $ImportOnly -and -not $Global:GuiSessionActive) { if ($isMultiScript) { Write-Host '' $tableRows = $Global:ExecutionLog | ForEach-Object { - @{ - Operazione = $_.Name - Stato = if ($_.Success) { '✅ Completato' } else { '❌ Errore' } - Dettaglio = if ($_.Error) { $_.Error } else { '' } - } + @{ Operazione = $_.Name; Stato = if ($_.Success) { '✅ Completato' } else { '❌ Errore' }; Dettaglio = if ($_.Error) { $_.Error } else { '' } } } - $tableCols = @( + Show-ConsoleTable -Rows $tableRows -Columns @( @{ Header = 'Operazione'; Key = 'Operazione' }, @{ Header = 'Stato'; Key = 'Stato' }, @{ Header = 'Dettaglio'; Key = 'Dettaglio' } - ) - Show-ConsoleTable -Rows $tableRows -Columns $tableCols -Title '📊 Riepilogo Esecuzione' + ) -Title '📊 Riepilogo Esecuzione' Write-Host '' } if ($Global:NeedsFinalReboot) { diff --git a/asset/AMD-Autodetect.exe b/asset/AMD-Autodetect.exe deleted file mode 100644 index fb1419c8..00000000 Binary files a/asset/AMD-Autodetect.exe and /dev/null differ diff --git a/asset/DriverOverrides.json b/asset/DriverOverrides.json new file mode 100644 index 00000000..af1a0664 --- /dev/null +++ b/asset/DriverOverrides.json @@ -0,0 +1,11 @@ +[ + { + "Key": "AMD_RX_580", + "Manufacturer": "AMD", + "NamePattern": "RX 580|RX-580|Radeon RX 580", + "PnpIdPattern": "PCI\\VEN_1002&DEV_67DF*", + "DownloadUrl": "https://drivers.amd.com/drivers/whql-amd-software-adrenalin-edition-23.8.2-win10-win11-aug31.exe", + "FileName": "AMD-RX580-23.8.2-WHQL.exe", + "DisplayName": "AMD RX 580 Stable Driver (23.8.2 WHQL)" + } +] diff --git a/asset/Microsoft.PowerShell_profile.ps1 b/asset/Microsoft.PowerShell_profile.ps1 index 3f91aa3e..32b8a8fd 100644 --- a/asset/Microsoft.PowerShell_profile.ps1 +++ b/asset/Microsoft.PowerShell_profile.ps1 @@ -13,7 +13,7 @@ # CONFIGURAZIONE CENTRALIZZATA (URL) # ============================================================================ -$ProfileVersion = "2.5.3.1" +$ProfileVersion = "2.5.4.5" $URL_SPEEDTEST = "https://github.com/Magnetarman/WinToolkit/raw/refs/heads/Dev/asset/speedtest.exe" $URL_WINTOOLKIT_STABLE = "https://magnetarman.com/WinToolkit" @@ -21,10 +21,10 @@ $URL_WINTOOLKIT_DEV = "https://magnetarman.com/WinToolkit-Dev" $URL_WINREG = "https://get.activated.win" $URL_RustDesk_Setup = "https://raw.githubusercontent.com/Magnetarman/WinStarter/refs/heads/main/Asset/RustDesk/SetRustDesk.ps1" $URL_OHMYPOSH_THEME = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/atomic.omp.json" -$URL_PROFILE = "https://github.com/Magnetarman/WinToolkit/raw/refs/heads/Dev/asset/Microsoft.PowerShell_profile.ps1" +$URL_PROFILE_DEV = "https://github.com/Magnetarman/WinToolkit/raw/refs/heads/Dev/asset/Microsoft.PowerShell_profile.ps1" $URL_IP_API = "https://am.i.mullvad.net/ip" $URL_WINTOOLKIT_ICO_MAIN = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/img/WinToolkit.ico" -$URL_WINTOOLKIT_ICO_DEV = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/Dev/img/WinToolkit-Dev.ico" +$URL_WINTOOLKIT_ICO_DEV = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/img/WinToolkit-Dev.ico" $URL_PROFILE_MAIN = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/Microsoft.PowerShell_profile.ps1" $URL_PWSH_RELEASE_API = "https://api.github.com/repos/PowerShell/PowerShell/releases/latest" @@ -266,7 +266,7 @@ function PSProfileUpdate { param() $localProfilePath = $PROFILE - $remoteProfileUrl = $URL_PROFILE + $remoteProfileUrl = $URL_PROFILE_MAIN Write-Host "🔍 Verifica aggiornamenti per il profilo PowerShell..." -ForegroundColor Cyan @@ -344,6 +344,10 @@ function WinToolkit-Dev { Start-Process -FilePath "wt.exe" -ArgumentList "new-tab -p `"PowerShell`" pwsh.exe -NoExit -ExecutionPolicy Bypass -Command `"irm $URL_WINTOOLKIT_DEV | iex`"" -Verb RunAs } +function WinToolkit-GUI { + Start-Process -FilePath "wt.exe" -ArgumentList "new-tab -p `"PowerShell`" pwsh.exe -NoExit -ExecutionPolicy Bypass -Command `"irm https://magnetarman.com/Wintoolkit-gui | iex`"" -Verb RunAs +} + function SetBranch-Dev { [CmdletBinding()] param() @@ -390,7 +394,7 @@ function SetBranch-Dev { Write-Host "⬇️ Download del profilo PowerShell dal ramo dev..." -ForegroundColor Cyan # Sovrascrive il profilo senza chiedere conferma - Invoke-WebRequest -Uri $URL_PROFILE -OutFile $PROFILE -UseBasicParsing + Invoke-WebRequest -Uri $URL_PROFILE_DEV -OutFile $PROFILE -UseBasicParsing Write-Host "✅ Profilo PowerShell sovrascritto con la versione dev." -ForegroundColor Green } catch { @@ -723,9 +727,9 @@ function Show-Help { $helpText = @" $($PSStyle.Foreground.Cyan)Guida al Profilo PowerShell$($PSStyle.Reset) $($PSStyle.Foreground.Red)========================================================$($PSStyle.Reset) -$($PSStyle.Foreground.Green)Verde (Safe):$($PSStyle.Reset) utilizzo sicuro, non comporta problematiche. -$($PSStyle.Foreground.Yellow)Giallo (Warning):$($PSStyle.Reset) Attenzione leggere attentamente la descrizione, questo tipo di comandi comportano variazioni distruttive al sistema. -$($PSStyle.Foreground.Red)Rosso (ALLERT!):$($PSStyle.Reset) Queste funzioni sono state designare per effettuare modifiche profonde e distruttive, attento a cosa stai facendo! +$($PSStyle.Foreground.Green)Verde (Safe):$($PSStyle.Reset) L'utilizzo non comporta rischi o problemi. +$($PSStyle.Foreground.Yellow)Giallo (Warning):$($PSStyle.Reset) Attenzione! Leggere la descrizione perché questi comandi comportano variazioni rischiose al sistema. +$($PSStyle.Foreground.Red)Rosso (ALERT!):$($PSStyle.Reset) STOP! Queste funzioni sono state designare per effettuare modifiche profonde e distruttive. Attento! $($PSStyle.Foreground.Green)====================================================================================$($PSStyle.Reset) @@ -754,6 +758,7 @@ $($PSStyle.Foreground.Green)ShutdownComplete$($PSStyle.Reset) - Spegnim $($PSStyle.Foreground.Cyan)Lancio WinToolkit$($PSStyle.Reset) $($PSStyle.Foreground.Yellow)------------------------------------------------------------------$($PSStyle.Reset) $($PSStyle.Foreground.Green)WinToolkit-Stable$($PSStyle.Reset) - Lancia WinToolkit (stabile). $($PSStyle.Foreground.Yellow)WinToolkit-Dev$($PSStyle.Reset) - Lancia WinToolkit (Dev). +$($PSStyle.Foreground.Magenta)WinToolkit-GUI$($PSStyle.Reset) - Lancia WinToolkit (Versione GUI). $($PSStyle.Foreground.Yellow)SetBranch-Main$($PSStyle.Reset) - Switcha l'ambiente (Icona e Profilo) al ramo main. $($PSStyle.Foreground.Yellow)SetBranch-Dev$($PSStyle.Reset) - Switcha l'ambiente (Icona e Profilo) al ramo dev. $($PSStyle.Foreground.Red)WinReg$($PSStyle.Reset) - Attiva Windows/Office (MAS). @@ -842,7 +847,7 @@ function Update-Pwsh { # Step 1: Disinstallazione Write-Host " 1/2 - Disinstallazione di Microsoft.PowerShell in corso..." -ForegroundColor Cyan - winget uninstall --id Microsoft.PowerShell --accept-source-agreements --silent + winget uninstall --id Microsoft.PowerShell --accept-source-agreements --silent --all-versions if ($LASTEXITCODE -ne 0) { Write-Host "❌ Disinstallazione fallita (codice: $LASTEXITCODE). Operazione interrotta." -ForegroundColor Red Write-Host " Prova a disinstallare PowerShell manualmente e poi esegui nuovamente Update-Pwsh." -ForegroundColor DarkYellow @@ -901,17 +906,20 @@ if (-not (Test-Path $localThemePath)) { } if (Test-Path $localThemePath) { - oh-my-posh init pwsh --config $localThemePath | Invoke-Expression + $ompScript = oh-my-posh init pwsh --config $localThemePath | Out-String + . ([ScriptBlock]::Create($ompScript)) } else { $fallbackUrl = $URL_OHMYPOSH_THEME Write-Warning "Tema locale non disponibile. Uso fallback remoto." - oh-my-posh init pwsh --config $fallbackUrl | Invoke-Expression + $ompScript = oh-my-posh init pwsh --config $fallbackUrl | Out-String + . ([ScriptBlock]::Create($ompScript)) } # zoxide if (Test-CommandExists -Name "zoxide") { - Invoke-Expression (& { (zoxide init powershell | Out-String) }) + $zoxideScript = zoxide init powershell | Out-String + . ([ScriptBlock]::Create($zoxideScript)) } # fastfetch diff --git a/asset/OOSU10.exe b/asset/OOSU10.exe deleted file mode 100644 index fb2b8c94..00000000 Binary files a/asset/OOSU10.exe and /dev/null differ diff --git a/asset/SaRACmd_17_01_2877_000.zip b/asset/SaRACmd_17_01_2877_000.zip deleted file mode 100644 index 74105c19..00000000 Binary files a/asset/SaRACmd_17_01_2877_000.zip and /dev/null differ diff --git a/asset/ooshutup10.cfg b/asset/ooshutup10.cfg deleted file mode 100644 index 80a5ecb9..00000000 --- a/asset/ooshutup10.cfg +++ /dev/null @@ -1,246 +0,0 @@ -############################################################################ -# Questo file è stato creato con O&O ShutUp10++ V2.1.1015 -# e può essere importato su un altro computer. -# -# Scaricare l'applicazione a https://www.oo-software.com/shutup10 -# Dopo sarà possibile importare il file dall'interno del programma. -# -# In alternativa, puoi importarlo automaticamente su una riga di comando. -# Basta usare i seguenti parametri: -# OOSU10.exe -# -# In seguito alla selezione dell’opzione /quiet -# L’applicazione termina l'importazione e l'utente non -# riceve nessun feedback a proposito. -# -# Siamo sempre liete di rispondere alle tue domande ! -# © 2015-2025 O&O Software GmbH, Berlin. All rights reserved. -# https://www.oo-software.com/ -############################################################################ - -P001 + # Condivisione dei dati scritti a mano disattivato (Categoria: Privacy) -P002 + # Condivisione dei rapporti d'errore di scrittura a mano disattivata (Categoria: Privacy) -P003 + # Inventory Collector disattivata (Categoria: Privacy) -P004 + # Fotocamera in schermata login disattivata (Categoria: Privacy) -P005 + # Disattivare e reimpostare l'ID pubblicità per l'intero computer (Categoria: Privacy) -P006 + # Disabilitare e reimpostare l'ID annuncio per l'utente corrente (Categoria: Privacy) -P008 + # Trasmissione di dati sulla digitazione disattivata (Categoria: Privacy) -P026 + # Disattivare le publicità tramite Bluetooth (Categoria: Privacy) -P027 + # Disattiva il Customer Experience Improvement Program di Windows (Categoria: Privacy) -P028 + # Disattiva il backup dei messaggi di SMS nel cloud (Categoria: Privacy) -P064 + # Disabilitare i suggerimenti nella sequenza temporale (Categoria: Privacy) -P065 + # Disabilitare i suggerimenti in Start (Categoria: Privacy) -P066 + # Disattivare suggerimenti, trucchi e suggerimenti quando si usa Windows (Categoria: Privacy) -P067 + # Disabilitare la visualizzazione dei contenuti suggeriti nell'app Impostazioni (Categoria: Privacy) -P070 + # Disabilitare la possibilità di suggerire di completare la configurazione del dispositivo (Categoria: Privacy) -P069 + # Disattiva Segnalazione errori Windows (Categoria: Privacy) -P009 - # Caratteristiche biometriche disattivata (Categoria: Privacy) -P010 - # App notifiche disattivato (Categoria: Privacy) -P015 - # L'accesso alla lingua locale per i browser disattivato (Categoria: Privacy) -P068 - # Disabilitare i suggerimenti di testo quando si digita sulla tastiera del software (Categoria: Privacy) -P016 - # L'invio dei URL tramite delle app al Windows Store disattivato (Categoria: Privacy) -A001 + # Disabilitare le registrazioni delle attività dell'utente (Categoria: Cronologia attività e Appunti) -A002 + # Archiviazione dIsable della cronologia delle attività degli utenti in questo dispositivoDIsable storing users' activity history on this device (Categoria: Cronologia attività e Appunti) -A003 + # Disabilitare l'invio delle attività degli utenti a Microsoft (Categoria: Cronologia attività e Appunti) -A004 + # Disabilitare l'archiviazione della cronologia degli Appunti per interi computer (Categoria: Cronologia attività e Appunti) -A006 + # Disabilitare l'archiviazione della cronologia degli Appunti per gli utenti correnti (Categoria: Cronologia attività e Appunti) -A005 + # Disabilitare il trasferimento degli Appunti ad altri dispositivi tramite il cloud (Categoria: Cronologia attività e Appunti) -P007 + # Disabilitare l'accesso dell'app alle informazioni sull'account utente in questo dispositivoDisable app access to user account information on this device (Categoria: App Privacy) -P036 + # Disabilitare l'accesso dell'app alle informazioni sull'account utente per l'utente corrente (Categoria: App Privacy) -P025 + # Disattivare il Windows tracking dei avviamenti delle app (Categoria: App Privacy) -P033 + # Disabilitare l'accesso dell'app alle informazioni di diagnostica per il dispositivo (Categoria: App Privacy) -P023 + # Disabilitare l'accesso dell'app alle informazioni di diagnostica per l'utente corrente (Categoria: App Privacy) -P056 + # Disabilitare l'accesso dell'app alla posizione del dispositivo in questo dispositivoDisable app access to device location on this device (Categoria: App Privacy) -P057 + # Disabilitare l'accesso dell'app alla posizione del dispositivo per l'utente corrente (Categoria: App Privacy) -P012 - # Disabilitare l'accesso dell'app alla fotocamera su questo dispositivo (Categoria: App Privacy) -P034 - # App accesso alla fotocamera disattivata (Categoria: App Privacy) -P013 - # Disabilitare l'accesso dell'app al microfono su questo dispositivo (Categoria: App Privacy) -P035 - # App accesso al microfono disattivata (Categoria: App Privacy) -P062 + # Disabilitare l'accesso alle app per usare l'attivazione vocale per l'utente corrente (Categoria: App Privacy) -P063 + # Disabilitare l'accesso all'app per usare l'attivazione vocale quando il dispositivo è bloccato per l'utente corrente (Categoria: App Privacy) -P081 - # Disabilita l'app predefinita per la pressione del pulsante dell'auricolare (Categoria: App Privacy) -P047 - # Disabilitare l'accesso dell'app alle notifiche su questo dispositivo (Categoria: App Privacy) -P019 - # Disattivare l'accesso delle app alle notifiche (Categoria: App Privacy) -P048 - # Disabilitare l'accesso dell'app al movimento in questo dispositivo (Categoria: App Privacy) -P049 + # Disabilitare l'accesso dell'app ai movimenti per l'utente corrente (Categoria: App Privacy) -P020 - # Disabilitare l'accesso alle app ai contatti su questo dispositivo (Categoria: App Privacy) -P037 + # Disabilitare l'accesso dell'app ai contatti per l'utente corrente (Categoria: App Privacy) -P011 - # Disabilitare l'accesso dell'app al calendario in questo dispositivo (Categoria: App Privacy) -P038 + # Disabilitare l'accesso dell'app al calendario per l'utente corrente (Categoria: App Privacy) -P050 - # Disabilitare l'accesso dell'app alle chiamate telefoniche su questo dispositivo (Categoria: App Privacy) -P051 + # Disabilitare l'accesso dell'app alle chiamate telefoniche per l'utente corrente (Categoria: App Privacy) -P018 - # Disabilitare l'accesso dell'app alla cronologia delle chiamate su questo dispositivo (Categoria: App Privacy) -P039 + # Disabilitare l'accesso dell'app alla cronologia delle chiamate per l'utente corrente (Categoria: App Privacy) -P021 - # Disabilitare l'accesso dell'app alla posta elettronica in questo dispositivoDisable app access to email on this device (Categoria: App Privacy) -P040 - # Disattivare l'accesso delle app alla posta elettronica (Categoria: App Privacy) -P022 - # Disabilitare l'accesso dell'app alle attività in questo dispositivo (Categoria: App Privacy) -P041 - # Disabilitare l'accesso delle app alle attività per l'utente corrente (Categoria: App Privacy) -P014 - # Disabilitare l'accesso dell'app ai messaggi in questo dispositivoDisable app access to messages on this device (Categoria: App Privacy) -P042 + # Disabilitare l'accesso dell'app ai messaggi per l'utente corrente (Categoria: App Privacy) -P052 - # Disabilitare l'accesso alle app alle radio su questo dispositivo (Categoria: App Privacy) -P053 - # Disabilitare l'accesso delle app alle radio per l'utente corrente (Categoria: App Privacy) -P054 - # Disabilita l'accesso all'app ai dispositivi non abbinati su questo dispositivo (Categoria: App Privacy) -P055 - # Disabilita l'accesso all'app ai dispositivi non associati per l'utente corrente (Categoria: App Privacy) -P029 - # Disabilitare l'accesso alle app ai documenti in questo dispositivo (Categoria: App Privacy) -P043 - # Disabilitare l'accesso dell'app ai documenti per l'utente corrente (Categoria: App Privacy) -P030 - # Disabilitare l'accesso alle app alle immagini in questo dispositivo (Categoria: App Privacy) -P044 - # Disabilitare l'accesso dell'app alle immagini per l'utente corrente (Categoria: App Privacy) -P031 - # Disabilitare l'accesso alle app ai video su questo dispositivo (Categoria: App Privacy) -P045 - # Disabilitare l'accesso alle app ai video per l'utente corrente (Categoria: App Privacy) -P032 - # Disabilitare l'accesso dell'app al file system su questo dispositivo (Categoria: App Privacy) -P046 - # Disabilitare l'accesso dell'app al file system per l'utente corrente (Categoria: App Privacy) -P058 - # Disabilita l'accesso tramite app alle apparecchiature wireless su questo dispositivo (Categoria: App Privacy) -P059 - # Disabilita l'accesso dell'app alla tecnologia wireless per l'utente corrente (Categoria: App Privacy) -P060 - # Disabilitare l'accesso alle app per il monitoraggio otculare su questo dispositivo (Categoria: App Privacy) -P061 + # Disabilitare l'accesso dell'app al monitoraggio oculare per l'utente corrente (Categoria: App Privacy) -P071 - # Disabilita la possibilità per le app di acquisire schermate su questo dispositivo (Categoria: App Privacy) -P072 - # Disabilita la possibilità per le app di acquisire schermate per l'utente corrente (Categoria: App Privacy) -P073 - # Disabilita la possibilità per le app desktop di acquisire schermate per l'utente corrente (Categoria: App Privacy) -P074 - # Disabilita la possibilità per le app di acquisire schermate senza bordi su questo dispositivo (Categoria: App Privacy) -P075 - # Disabilita la possibilità per le app di acquisire schermate senza bordi per l'utente corrente (Categoria: App Privacy) -P076 - # Disabilita la possibilità per le app desktop di acquisire schermate senza margini per l'utente corrente (Categoria: App Privacy) -P077 - # Disabilita l'accesso delle app alle librerie musicali su questo dispositivo (Categoria: App Privacy) -P078 - # Disabilitare l'accesso delle app alle librerie musicali per l'utente corrente (Categoria: App Privacy) -P079 - # Disabilita l'accesso dell'app alla cartella dei download su questo dispositivo (Categoria: App Privacy) -P080 - # Disabilitare l'accesso dell'app alla cartella dei download per l'utente corrente (Categoria: App Privacy) -P024 + # Bloccare l'esecuzione delle app in background (Categoria: App Privacy) -S001 + # Pulsante Rivela password disattivato (Categoria: Sicurezza) -S002 + # Registrazione azioni utente disattivata (Categoria: Sicurezza) -S003 + # Telemetria disattivata (Categoria: Sicurezza) -S008 - # L'accesso a Internet di Windows Media Digital Rights Management ( DRM ) disattivata (Categoria: Sicurezza) -E101 + # Disattiva il tracciamento sul web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E201 + # Disattiva il tracciamento sul web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E115 + # Disabilitare il controllo dei metodi di pagamento salvati per i siti (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E215 + # Disabilitare il controllo dei metodi di pagamento salvati per i siti (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E118 + # Disabilitare la personalizzazione di pubblicità, ricerca, notizie e altri servizi (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E218 + # Disabilitare la personalizzazione di pubblicità, ricerca, notizie e altri servizi (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E107 + # Disattivare il completamento automatico degli indirizzi web nella barra degli indirizzi (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E207 + # Disattivare il completamento automatico degli indirizzi web nella barra degli indirizzi (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E111 + # Disabilita il feedback degli utenti nella barra degli strumenti (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E211 + # Disabilita il feedback degli utenti nella barra degli strumenti (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E112 + # Disattivare l'archiviazione automatica e il riempimento dei dati della carta di credito (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E212 + # Disattivare l'archiviazione automatica e il riempimento dei dati della carta di credito (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E109 + # Disattiva il completamento automatico (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E209 + # Disattiva il completamento automatico (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E121 + # Disabilitare i suggerimenti dei provider locali (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E221 + # Disabilitare i suggerimenti dei provider locali (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E103 + # Disattiva suggerimenti per la ricerca e I siti web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E203 + # Disattiva suggerimenti per la ricerca e I siti web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E123 + # Disabilitare l'Assistente acquisti in Microsoft Edge (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E223 + # Disabilitare l'Assistente acquisti in Microsoft Edge (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E124 + # Disabilitare la barra dei bordi (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E224 + # Disabilitare la barra dei bordi (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E128 + # Disattivare la barra laterale in Microsoft Edge (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E228 + # Disattivare la barra laterale in Microsoft Edge (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E129 + # Disabilita il pulsante di accesso all'account Microsoft (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E229 + # Disabilita il pulsante di accesso all'account Microsoft (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E130 + # Disabilita il controllo ortografico avanzato (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E230 + # Disabilita il controllo ortografico avanzato (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E119 + # Disabilitare l'utilizzo del servizio Web per risolvere gli errori di navigazione (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E219 + # Disabilitare l'utilizzo del servizio Web per risolvere gli errori di navigazione (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E120 + # Disattiva il suggerimento di siti simili quando non è possibile trovare un sito Web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E220 + # Disattiva il suggerimento di siti simili quando non è possibile trovare un sito Web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E122 + # Disabilitare il precaricamento delle pagine per una navigazione e una ricerca più veloci (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E222 + # Disabilitare il precaricamento delle pagine per una navigazione e una ricerca più veloci (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E125 + # Disabilitare il salvataggio delle password per i siti Web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E225 + # Disabilitare il salvataggio delle password per i siti Web (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E126 + # Disattivare i servizi di sicurezza del sito per ulteriori informazioni su un sito Web visitato (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E226 + # Disattivare i servizi di sicurezza del sito per ulteriori informazioni su un sito Web visitato (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E131 + # Disabilita il reindirizzamento automatico da Internet Explorer a Microsoft Edge (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E106 + # Disattiva filtro SmartScreen (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E206 + # Disattiva filtro SmartScreen (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E127 + # Disabilitare il controllo errori di battitura per gli indirizzi dei siti (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E227 + # Disabilitare il controllo errori di battitura per gli indirizzi dei siti (Categoria: Microsoft Edge (nuova versione basata su Chromium)) -E001 + # Disattiva il tracciamento sul web (Categoria: Microsoft Edge (versione legacy)) -E002 + # Disattiva pagina previsione (Categoria: Microsoft Edge (versione legacy)) -E003 + # Disattiva suggerimenti per la ricerca e I siti web (Categoria: Microsoft Edge (versione legacy)) -E008 + # Disattiva Cortana in Microsoft Edge (Categoria: Microsoft Edge (versione legacy)) -E007 + # Disattivare il completamento automatico degli indirizzi web nella barra degli indirizzi (Categoria: Microsoft Edge (versione legacy)) -E010 + # Disattiva la visualizzazione della cronologia delle ricerche (Categoria: Microsoft Edge (versione legacy)) -E011 + # Disabilita il feedback degli utenti nella barra degli strumenti (Categoria: Microsoft Edge (versione legacy)) -E012 + # Disattivare l'archiviazione automatica e il riempimento dei dati della carta di credito (Categoria: Microsoft Edge (versione legacy)) -E009 + # Disattiva il completamento automatico (Categoria: Microsoft Edge (versione legacy)) -E004 + # Disattiva il salvataggio delle licenze multimediali protetti sul mio dispositivo (Categoria: Microsoft Edge (versione legacy)) -E005 + # Non ottimizzare i risultati di ricerca web sulla barra delle applicazioni per lettore di schermo (Categoria: Microsoft Edge (versione legacy)) -E013 + # Disattivare Microsoft Edge in background (Categoria: Microsoft Edge (versione legacy)) -E014 + # Disattivare il caricamento delle schede iniziale e nuova in background (Categoria: Microsoft Edge (versione legacy)) -E006 + # Disattiva filtro SmartScreen (Categoria: Microsoft Edge (versione legacy)) -Y001 + # Disattiva la sincronizzazione di tutte le impostazioni (Categoria: Sincronizzazione di Impostazioni di Windows) -Y002 + # Disattiva la sincronizzazione delle impostazioni di progetto (Categoria: Sincronizzazione di Impostazioni di Windows) -Y003 + # Disattivare la sincronizzazione delle impostazioni del browser (Categoria: Sincronizzazione di Impostazioni di Windows) -Y004 + # Disattiva la sincronizzazione delle credenziali ( password ) (Categoria: Sincronizzazione di Impostazioni di Windows) -Y005 + # Disattiva la sincronizzazione delle impostazioni di lingua (Categoria: Sincronizzazione di Impostazioni di Windows) -Y006 + # Disattiva la sincronizzazione delle impostazioni di accessibilità (Categoria: Sincronizzazione di Impostazioni di Windows) -Y007 + # Disattiva la sincronizzazione delle impostazioni avanzate di Windows (Categoria: Sincronizzazione di Impostazioni di Windows) -C012 + # Cortana reset e disattivata (Categoria: Cortana (Assistente personale)) -C002 + # Input personalizzata disattivato (Categoria: Cortana (Assistente personale)) -C013 + # Disabilitare il riconoscimento vocale online (Categoria: Cortana (Assistente personale)) -C007 + # Cortana e Ricerca non sono autorizzati a utilizzare la posizione (Categoria: Cortana (Assistente personale)) -C008 + # Ricerca web dal Windows Desktop Search disattivata (Categoria: Cortana (Assistente personale)) -C009 + # La visualizzazione dei risultati web in Ricerca disattivata (Categoria: Cortana (Assistente personale)) -C010 + # Disattivare i download e aggiornamenti dei modeli di riconoscimento e di sintesi vocale (Categoria: Cortana (Assistente personale)) -C011 + # Disattiva la ricerca cloud (Categoria: Cortana (Assistente personale)) -C014 + # Disabilita Cortana sopra la schermata di blocco (Categoria: Cortana (Assistente personale)) -C015 + # Disattivazione delle evidenziazioni di ricerca nella barra delle applicazioni (Categoria: Cortana (Assistente personale)) -C101 + # Disattivazione del copilota di Windows (Categoria: Windows AI) -C201 + # Disattivazione del copilota di Windows (Categoria: Windows AI) -C204 + # Disabilitare la fornitura della funzionalità di richiamo a tutti gli utenti (Categoria: Windows AI) -C205 + # Disabilita il creatore di immagini in Microsoft Paint (Categoria: Windows AI) -C102 + # Disabilitazione del pulsante Copilot dalla barra delle applicazioni (Categoria: Windows AI) -C103 + # Disabilita il richiamo di Windows Copilot+ (Categoria: Windows AI) -C203 + # Disabilita il richiamo di Windows Copilot+ (Categoria: Windows AI) -C206 + # Disabilita Cocreator in Microsoft Paint (Categoria: Windows AI) -C207 + # Disabilita il riempimento dell'immagine basato sull'intelligenza artificiale in Microsoft Paint (Categoria: Windows AI) -L001 + # Funzionalità per individuare il sistema (Categoria: Servizi individuare) -L003 + # Funzionalità di scripting per individuare il sistema disattivata (Categoria: Servizi individuare) -L004 - # Sensori per individuare il sistema e il suo orientamento (Categoria: Servizi individuare) -L005 + # Windows Geolocation Service disattivata (Categoria: Servizi individuare) -U001 + # App di telemetria disattivata (Categoria: Comportamento dell'utente) -U004 + # Disattivare che I dati di diagnostica personalizza l'esperienza degli utenti (Categoria: Comportamento dell'utente) -U005 + # Disabilitare l'uso dei dati di diagnostica per un'esperienza utente personalizzata per gli utenti correnti (Categoria: Comportamento dell'utente) -U006 + # Disabilitare la raccolta dei log di diagnostica (Categoria: Comportamento dell'utente) -U007 + # Disabilitare il download delle impostazioni di configurazione di OneSettings (Categoria: Comportamento dell'utente) -W001 + # Windows Update tramite peer-to-peer disattivata (Categoria: Windows Update) -W011 + # Disattiva gli aggiornamenti ai moduli di riconoscimento vocale e sintesi vocale. (Categoria: Windows Update) -W004 + # Attiva differimento degli aggiornamenti (Categoria: Windows Update) -W005 + # Disattiva il download automatico delle applicazioni e le icone dei produttori per i dispositivi (Categoria: Windows Update) -W010 + # Aggiornamenti automatici dei driver tramite Windows Update disattivata (Categoria: Windows Update) -W009 + # Aggiornamenti automatici per le app tramite Windows Update disattivata (Categoria: Windows Update) -P017 - # Disabilitare la configurazione dinamica di Windows e le implementazioni degli aggiornamenti (Categoria: Windows Update) -W006 - # Aggiornamenti automatici di Windows Updates disattivata (Categoria: Windows Update) -W008 + # Disattivata Windows Update per altri prodotti ( ad esempio, Microsoft Office ) (Categoria: Windows Update) -M006 + # Disattivare la visualizzazione saltuaria dei suggerimenti per le app in menu Start (Categoria: Windows Explorer) -M011 + # Non visualizzare gli elementi aperti di recente in Elenchi di salti su "Start" o sulla barra delle applicazioni (Categoria: Windows Explorer) -M010 + # Disattiva publicità in Windows Explorer/OneDrive (Categoria: Windows Explorer) -O003 + # Disattiva l'accesso OneDrive alla rete prima del login (Categoria: Windows Explorer) -O001 - # Microsoft OneDrive disattivato (Categoria: Windows Explorer) -S012 + # Appartenenza a Microsoft SpyNet disattivata (Categoria: Microsoft Defender e Microsoft SpyNet) -S013 + # L’invio dei campioni di dati a Microsoft disattivato (Categoria: Microsoft Defender e Microsoft SpyNet) -S014 + # Notifica infezione da malware disattivata (Categoria: Microsoft Defender e Microsoft SpyNet) -K001 + # Disattivare Windows Spotlight (Categoria: Schermata di blocco) -K002 + # Disattiva divertimento, suggerimenti, trucchi e ancora di più sulla schermata di blocco (Categoria: Schermata di blocco) -K005 + # Disattiva le notifiche sulla schermata di blocco (Categoria: Schermata di blocco) -D001 - # Disabilitare l'accesso ai dispositivi mobili (Categoria: Dispositivi mobili) -D002 - # Disattivare l'applicazione Phone Link (Categoria: Dispositivi mobili) -D003 + # Disattivare la visualizzazione dei suggerimenti per l'utilizzo di dispositivi mobili con Windows (Categoria: Dispositivi mobili) -D104 - # Disattivare la connessione del PC ai dispositivi mobili (Categoria: Dispositivi mobili) -M025 + # Disattivazione della ricerca con AI nella casella di ricerca (Categoria: Ricerca) -M003 + # Estensione di Windows ricerca con Bing disattivata (Categoria: Ricerca) -M015 + # Disattiva l'icona Persone nella barra delle applicazioni (Categoria: Barra delle applicazioni) -M016 + # Disabilita la casella di ricerca nella barra delle applicazioni (Categoria: Barra delle applicazioni) -M017 + # Disabilitare "Meet now" nella barra delle applicazioni di questo dispositivo (Categoria: Barra delle applicazioni) -M018 + # Disabilitare "Meet now" nella barra delle applicazioni per l'utente corrente (Categoria: Barra delle applicazioni) -M019 + # Disabilitare notizie e interessi nella barra delle applicazioni (Categoria: Barra delle applicazioni) -M021 - # Disabilitare i widget in Esplora risorse (Categoria: Barra delle applicazioni) -M022 + # Disabilita i promemoria di feedback su questo dispositivo (Categoria: Miscellanea) -M001 + # Disabilitare i promemoria di feedback per l'utente corrente (Categoria: Miscellanea) -M004 + # Disattivare l'installazione automatica delle app raccomandati dal Windows Store (Categoria: Miscellanea) -M005 + # Disattivare i trucchi e suggerimenti durante l'uso di Windows (Categoria: Miscellanea) -M024 + # Disattivare la diagnostica di Windows Media Player (Categoria: Miscellanea) -M012 - # Disattiva il servizio di attivazione della gestione delle chiavi online (Categoria: Miscellanea) -M013 + # Disattiva il download automatico (Categoria: Miscellanea) -M014 + # Disattiva il traffico di rete non richiesto sulle pagina di mappe offline (Categoria: Miscellanea) -M026 + # Disabilita le connessioni di assistenza remota a questo computer (Categoria: Miscellanea) -M027 + # Disabilita le connessioni remote a questo computer (Categoria: Miscellanea) -M028 + # Disabilita l'icona del desktop per informazioni su "Windows Spotlight" (Categoria: Miscellanea) -N001 - # Network Connectivity Status Indicator disattivata (Categoria: Miscellanea) diff --git a/img/Run.jpg b/img/Run.jpg index ee0c36b7..0d685cdd 100644 Binary files a/img/Run.jpg and b/img/Run.jpg differ diff --git a/start.ps1 b/start.ps1 index 098fb593..22127e6f 100644 --- a/start.ps1 +++ b/start.ps1 @@ -8,36 +8,34 @@ #> # --- CONFIGURAZIONE GLOBALE --- -# $ErrorActionPreference = 'Stop' # Rimossa per resilienza globale -$Global:MsgStyles = @{ - Success = @{ Icon = '✅'; Color = 'Green' } - Warning = @{ Icon = '⚠️'; Color = 'Yellow' } - Error = @{ Icon = '❌'; Color = 'Red' } - Info = @{ Icon = '💎'; Color = 'Cyan' } - Progress = @{ Icon = '🔄'; Color = 'Magenta' } -} $script:AppConfig = @{ + MsgStyles = @{ + Success = @{ Icon = '✅'; Color = 'Green' } + Warning = @{ Icon = '⚠️'; Color = 'Yellow' } + Error = @{ Icon = '❌'; Color = 'Red' } + Info = @{ Icon = '💎'; Color = 'Cyan' } + } # ============================================================================ # HEADER CONFIGURATION - Modifica qui per aggiornare titolo e versione # ============================================================================ - Header = @{ + Header = @{ Title = "Toolkit Starter By MagnetarMan" - Version = "Version 2.5.3 (Build 13)" + Version = "Version 2.5.4 (Build 28)" } - URLs = @{ - StartScript = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/start.ps1" + URLs = @{ + StartScript = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/Dev/start.ps1" WingetMSIX = "https://aka.ms/getwinget" GitRelease = "https://api.github.com/repos/git-for-windows/git/releases/latest" PowerShellRelease = "https://api.github.com/repos/PowerShell/PowerShell/releases/latest" OhMyPoshTheme = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/atomic.omp.json" - PowerShellProfile = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/Microsoft.PowerShell_profile.ps1" - WindowsTerminalSettings = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/main/asset/settings.json" + PowerShellProfile = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/Dev/asset/Microsoft.PowerShell_profile.ps1" + WindowsTerminalSettings = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/Dev/asset/settings.json" ToolkitIcon = "https://raw.githubusercontent.com/Magnetarman/WinToolkit/refs/heads/main/img/WinToolkit.ico" TerminalRelease = "https://api.github.com/repos/microsoft/terminal/releases/latest" - WebInstaller = "https://magnetarman.com/WinToolkit" + WebInstaller = "https://magnetarman.com/WinToolkit-Dev" } - Paths = @{ + Paths = @{ Logs = "$env:LOCALAPPDATA\WinToolkit\logs" WinToolkitDir = "$env:LOCALAPPDATA\WinToolkit" Temp = "$env:TEMP\WinToolkitSetup" @@ -46,34 +44,84 @@ $script:AppConfig = @{ wtExe = "$env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe" wtDir = "$env:LOCALAPPDATA\Microsoft\WindowsApps" } - Registry = @{ + Registry = @{ TerminalStartup = "HKCU:\Console\%%Startup" } + WindowsTerminal = @{ + DelegationTerminalClsid = "{E12F0936-0E6F-548E-A9F6-B20C69A27D17}" + DelegationConsoleClsid = "{B23D10C0-31E3-401A-97EF-4BB30B62E10B}" + } + WingetProcesses = @( + 'WinStore.App', + 'wsappx', + 'AppInstaller', + 'Microsoft.WindowsStore', + 'Microsoft.DesktopAppInstaller', + 'winget', + 'WindowsPackageManagerServer' + ) + UpdateServices = @('wuauserv', 'bits', 'cryptsvc', 'dosvc') + Layout = @{ + Width = 65 + } } # ============================================================================ # FUNZIONI DI UTILITÀ & SUPPORTO WINGET # ============================================================================ -function Find-WinGet { + + +function Test-VCRedistInstalled { <# .SYNOPSIS - Finds the WinGet executable location. + Checks if Visual C++ Redistributable is installed and verifies the major version is 14. #> - try { - $wingetPathToResolve = Join-Path -Path $ENV:ProgramFiles -ChildPath 'Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe' - $resolveWingetPath = Resolve-Path -Path $wingetPathToResolve -ErrorAction Stop | Sort-Object { - [version]($_.Path -replace '^[^\d]+_((\d+\.)*\d+)_.*', '$1') - } - if ($resolveWingetPath) { - $wingetPath = $resolveWingetPath[-1].Path + $64BitOS = [System.Environment]::Is64BitOperatingSystem + $checksPassed = 0 + + # Controlliamo sempre la versione 32bit (esiste sempre su tutti i sistemi) + $registryPath32 = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86' + $dllPath32 = "$env:windir\syswow64\concrt140.dll" + + if ((Test-Path -Path $registryPath32) -and + ((Get-ItemProperty -Path $registryPath32 -Name 'Major' -ErrorAction SilentlyContinue).Major -eq 14) -and + [System.IO.File]::Exists($dllPath32)) { + $checksPassed++ + } + + # Se il sistema è 64bit controlliamo ANCHE la versione 64bit + if ($64BitOS) { + $registryPath64 = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64' + $dllPath64 = "$env:windir\system32\concrt140.dll" + + if ((Test-Path -Path $registryPath64) -and + ((Get-ItemProperty -Path $registryPath64 -Name 'Major' -ErrorAction SilentlyContinue).Major -eq 14) -and + [System.IO.File]::Exists($dllPath64)) { + $checksPassed++ } + } - $wingetExe = Join-Path $wingetPath 'winget.exe' + # Su sistema 32bit: basta che sia presente la versione 32bit + # Su sistema 64bit: devono essere presenti ENTRAMBE le versioni 32 + 64 bit + $requiredChecks = if ($64BitOS) { 2 } else { 1 } + + return $checksPassed -eq $requiredChecks +} - if (Test-Path -Path $wingetExe) { - return $wingetExe +function Get-WinGetFolder { + <# + .SYNOPSIS + Trova la cartella di installazione ufficiale di Winget piu' recente. + #> + try { + $arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" } + $wingetDir = Get-ChildItem -Path "$env:ProgramFiles\WindowsApps" -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | + Sort-Object { [version]($_.Name -replace '^[^\d]+_((\d+\.)*\d+)_.*', '$1') } -Descending | Select-Object -First 1 + + if ($wingetDir) { + return $wingetDir.FullName } return $null } @@ -82,44 +130,34 @@ function Find-WinGet { } } -function Test-VCRedistInstalled { +function Get-WinGetExecutable { <# .SYNOPSIS - Checks if Visual C++ Redistributable is installed and verifies the major version is 14. + Ottiene il percorso valido di winget.exe, con fallback diretto. #> - - $64BitOS = [System.Environment]::Is64BitOperatingSystem - $64BitProcess = [System.Environment]::Is64BitProcess - - # Require running system native process - if ($64BitOS -and -not $64BitProcess) { - Write-StyledMessage -Type Warning -Text "Esegui PowerShell nativo (x64)." - # Rimosso early return `$false` in modo da procedere comunque + # Prova prima il percorso standard alias + $aliasPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" + if (Test-Path $aliasPath) { + return $aliasPath } - # Check registry - $registryPath = [string]::Format( - 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\{0}\Microsoft\VisualStudio\14.0\VC\Runtimes\X{1}', - $(if ($64BitOS -and $64BitProcess) { 'WOW6432Node' } else { '' }), - $(if ($64BitOS) { '64' } else { '86' }) - ) - - $registryExists = Test-Path -Path $registryPath - - # Check major version - $majorVersion = if ($registryExists) { - (Get-ItemProperty -Path $registryPath -Name 'Major' -ErrorAction SilentlyContinue).Major + # Fallback: percorso diretto nella cartella di installazione + $wingetFolder = Get-WinGetFolder + if ($wingetFolder) { + $exePath = Join-Path $wingetFolder "winget.exe" + if (Test-Path $exePath) { + return $exePath + } } - else { 0 } - - # Check DLL exists - $dllPath = [string]::Format('{0}\system32\concrt140.dll', $env:windir) - $dllExists = [System.IO.File]::Exists($dllPath) - return $registryExists -and $majorVersion -eq 14 -and $dllExists + return $null } function Test-WingetCompatibility { + <# + .SYNOPSIS + Verifica la compatibilità del sistema operativo con Winget. + #> $osInfo = [Environment]::OSVersion $build = $osInfo.Version.Build @@ -135,6 +173,10 @@ function Test-WingetCompatibility { } function Test-WingetFunctionality { + <# + .SYNOPSIS + Verifica che Winget sia presente nel PATH e funzioni correttamente. + #> Write-StyledMessage -Type Info -Text "🔍 Verifica funzionalità Winget." # Aggiorna il PATH per rilevare installazioni recenti @@ -170,15 +212,7 @@ function Invoke-ForceCloseWinget { Write-StyledMessage -Type Info -Text "Chiusura processi interferenti." # Lista mirata dei processi che bloccano effettivamente l'installazione Appx - $interferingProcesses = @( - "WinStore.App", - "wsappx", - "AppInstaller", - "Microsoft.WindowsStore", - "Microsoft.DesktopAppInstaller", - "winget", - "WindowsPackageManagerServer" - ) + $interferingProcesses = $script:AppConfig.WingetProcesses foreach ($procName in $interferingProcesses) { Get-Process -Name $procName -ErrorAction SilentlyContinue | @@ -189,6 +223,46 @@ function Invoke-ForceCloseWinget { Write-StyledMessage -Type Success -Text "Processi interferenti chiusi." } +function Invoke-StopUpdateServices { + <# + .SYNOPSIS + Sospende temporaneamente i servizi di Windows Update e correlati per evitare conflitti con Winget. + #> + Write-StyledMessage -Type Info -Text "Sospensione temporanea servizi Windows Update per evitare conflitti." + $services = $script:AppConfig.UpdateServices + foreach ($svc in $services) { + if (Get-Service -Name $svc -ErrorAction SilentlyContinue) { + Write-StyledMessage -Type Info -Text "Arresto servizio: $svc..." + Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue + } + } + Write-StyledMessage -Type Success -Text "Servizi di aggiornamento sospesi correttamente." +} + +function Invoke-StartUpdateServices { + <# + .SYNOPSIS + Ripristina i servizi di Windows Update e correlati. + #> + Write-StyledMessage -Type Info -Text "Ripristino servizi Windows Update." + $services = $script:AppConfig.UpdateServices + foreach ($svc in $services) { + if (Get-Service -Name $svc -ErrorAction SilentlyContinue) { + Write-StyledMessage -Type Info -Text "Avvio servizio: $svc..." + try { + Start-Service -Name $svc -ErrorAction Stop + } + catch { + # Ignora avvertimenti di avvio in corso e servizi delayed + if ($_.Exception.Message -notmatch 'in corso') { + Write-ToolkitLog -Level 'Warning' -Message "Avvio servizio ${svc}: $($_.Exception.Message)" + } + } + } + } + Write-StyledMessage -Type Success -Text "Servizi di aggiornamento ripristinati." +} + function Set-WingetPathPermissions { <# .SYNOPSIS @@ -199,14 +273,7 @@ function Set-WingetPathPermissions { $wingetFolderPath = $null try { - # Find winget folder - $arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" } - $wingetDir = Get-ChildItem -Path "$env:ProgramFiles\WindowsApps" -Filter "Microsoft.DesktopAppInstaller_*_*${arch}__8wekyb3d8bbwe" -ErrorAction SilentlyContinue | - Sort-Object Name -Descending | Select-Object -First 1 - - if ($wingetDir) { - $wingetFolderPath = $wingetDir.FullName - } + $wingetFolderPath = Get-WinGetFolder } catch { } @@ -225,11 +292,15 @@ function Set-WingetPathPermissions { } function Repair-WingetDatabase { + <# + .SYNOPSIS + Esegue un ripristino completo del database e delle configurazioni di Winget. + #> Write-StyledMessage -Type Info -Text "🔧 Avvio ripristino database Winget." try { # 1. Ferma i processi interferenti - Stop-InterferingProcess + Invoke-ForceCloseWinget # 2. Pulizia cache locale di Winget $wingetCachePath = "$env:LOCALAPPDATA\WinGet" @@ -265,15 +336,13 @@ function Repair-WingetDatabase { } catch {} # Ignora errori durante il reset - # 5. Aggiorna il PATH - Update-EnvironmentPath - - # 6. Reset completo del pacchetto AppInstaller (Cruciale per ACCESS_VIOLATION) + # 5. Reset completo del pacchetto AppInstaller (Cruciale per ACCESS_VIOLATION) Write-StyledMessage -Type Info -Text "Reset pacchetto Microsoft.DesktopAppInstaller." if (Get-Command Reset-AppxPackage -ErrorAction SilentlyContinue) { Get-AppxPackage -Name 'Microsoft.DesktopAppInstaller' | Reset-AppxPackage 2>$null } + # 6. Re-registrazione manifest AppInstaller try { $manifest = (Get-AppxPackage -Name 'Microsoft.DesktopAppInstaller' -ErrorAction SilentlyContinue).InstallLocation if ($manifest) { @@ -283,7 +352,8 @@ function Repair-WingetDatabase { Start-AppxSilentProcess -AppxPath $manifestXml -Flags '-DisableDevelopmentMode -Register -ForceApplicationShutdown' | Out-Null } } - } catch { } + } + catch { } # 7. Riprova con il modulo WinGet se disponibile try { @@ -323,6 +393,10 @@ function Repair-WingetDatabase { } function Test-WingetDeepValidation { + <# + .SYNOPSIS + Esegue un test approfondito di connettività e funzionalità di Winget. + #> Write-StyledMessage -Type Info -Text "🔍 Esecuzione test profondo di Winget (ricerca pacchetti in rete)." try { @@ -377,47 +451,36 @@ function Test-WingetDeepValidation { # FUNZIONI DI INSTALLAZIONE # ============================================================================ -function Install-NuGetIfRequired { +function Get-WingetDownloadUrl { <# .SYNOPSIS - Checks if NuGet PackageProvider is installed and installs it if required. + Recupera l'URL di download dell'ultimo asset di Winget CLI da GitHub. #> - - if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { - if ($PSVersionTable.PSVersion.Major -lt 7) { - try { - Install-PackageProvider -Name "NuGet" -Force -ForceBootstrap -ErrorAction SilentlyContinue *>$null - Write-StyledMessage -Type Info -Text "NuGet provider installato." - } - catch { - Write-StyledMessage -Type Warning -Text "Impossibile installare NuGet provider." - } + param([string]$Match) + try { + $latest = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/winget-cli/releases/latest" -UseBasicParsing + $asset = $latest.assets | Where-Object { $_.name -match $Match } | Select-Object -First 1 + if ($asset) { + return $asset.browser_download_url } + throw "Asset '$Match' non trovato." + } + catch { + Write-StyledMessage -Type Warning -Text "Errore recupero URL asset: $($_.Exception.Message)." + return $null } } function Install-WingetCore { + <# + .SYNOPSIS + Esegue l'installazione minima e dipendenze core di Winget. + #> Write-StyledMessage -Type Info -Text "🛠️ Avvio procedura di ripristino Winget (Core)." $oldProgress = $ProgressPreference $ProgressPreference = 'SilentlyContinue' - function Get-WingetDownloadUrl { - param([string]$Match) - try { - $latest = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/winget-cli/releases/latest" -UseBasicParsing - $asset = $latest.assets | Where-Object { $_.name -match $Match } | Select-Object -First 1 - if ($asset) { - return $asset.browser_download_url - } - throw "Asset '$Match' non trovato." - } - catch { - Write-StyledMessage -Type Warning -Text "Errore recupero URL asset: $($_.Exception.Message)." - return $null - } - } - $tempDir = "$env:TEMP\WinToolkitWinget" if (-not (Test-Path $tempDir)) { New-Item -Path $tempDir -ItemType Directory -Force *>$null @@ -466,10 +529,10 @@ function Install-WingetCore { $archPattern = if ([Environment]::Is64BitOperatingSystem) { "x64|ne" } else { "x86|ne" } $appxFiles = Get-ChildItem -Path $extractPath -Recurse -Filter "*.appx" | Where-Object { $_.Name -match $archPattern } - $script:WingetDependencies = @() + $dependencies = @() foreach ($file in $appxFiles) { Write-StyledMessage -Type Info -Text "Trovata dipendenza: $($file.Name)." - $script:WingetDependencies += $file.FullName + $dependencies += $file.FullName } } catch { @@ -484,7 +547,7 @@ function Install-WingetCore { $wingetFile = Join-Path $tempDir "winget.msixbundle" Invoke-WebRequest -Uri $wingetUrl -OutFile $wingetFile -UseBasicParsing - $deps = if ($script:WingetDependencies) { $script:WingetDependencies } else { @() } + $deps = if ($dependencies) { $dependencies } else { @() } if (Start-AppxSilentProcess -AppxPath $wingetFile -DependencyPaths $deps -Flags '-ForceApplicationShutdown') { Write-StyledMessage -Type Success -Text "Winget Core installato con successo." } @@ -507,6 +570,10 @@ function Install-WingetCore { } function Install-WingetPackage { + <# + .SYNOPSIS + Procedura completa di installazione e ripristino di Winget. + #> param([switch]$Force) Write-StyledMessage -Type Info -Text "🚀 Avvio procedura installazione/verifica Winget." @@ -519,6 +586,7 @@ function Install-WingetPackage { Invoke-ForceCloseWinget try { + $oldProgress = $ProgressPreference $ProgressPreference = 'SilentlyContinue' # Pulizia temporanei @@ -535,11 +603,6 @@ function Install-WingetPackage { catch {} } - if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { - Write-StyledMessage -Type Info -Text "Verifica/installazione NuGet provider." - Install-NuGetIfRequired - } - if (-not (Get-Module -ListAvailable Microsoft.WinGet.Client) -or $Force) { Write-StyledMessage -Type Info -Text "Installazione modulo Microsoft.WinGet.Client." try { @@ -563,7 +626,8 @@ function Install-WingetPackage { catch { if ($_.Exception.Message -match '0x80073D06' -or $_.Exception.Message -match 'versione successiva') { Write-StyledMessage -Type Info -Text "Repair-WinGetPackageManager ignorato (versione superiore già presente)." - } else { + } + else { Write-StyledMessage -Type Warning -Text "Repair-WinGetPackageManager fallito: $($_.Exception.Message)." } } @@ -571,7 +635,6 @@ function Install-WingetPackage { } # Fallback finale: installazione via MSIXBundle - Update-EnvironmentPath if (-not (Get-Command winget -ErrorAction SilentlyContinue) -or $Force) { Write-StyledMessage -Type Info -Text "Download MSIXBundle da Microsoft." @@ -622,14 +685,18 @@ function Install-WingetPackage { return $false } finally { - $ProgressPreference = 'Continue' + $ProgressPreference = $oldProgress } } function Install-GitPackage { + <# + .SYNOPSIS + Verifica e installa Git con fallback a download diretto. + #> Write-StyledMessage -Type Info -Text "Verifica installazione Git..." - $env:Path = [Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [Environment]::GetEnvironmentVariable("Path", "User") + Update-EnvironmentPath if (Get-Command git -ErrorAction SilentlyContinue) { Write-StyledMessage -Type Success -Text "Git già installato." @@ -644,7 +711,7 @@ function Install-GitPackage { if ($result.ExitCode -eq 0) { Start-Sleep 3 - $env:Path = [Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [Environment]::GetEnvironmentVariable("Path", "User") + Update-EnvironmentPath if (Get-Command git -ErrorAction SilentlyContinue) { Write-StyledMessage -Type Success -Text "Git installato via winget." @@ -683,7 +750,7 @@ function Install-GitPackage { Remove-Item $installerPath -Force -ErrorAction SilentlyContinue if ($process.ExitCode -eq 0) { - $env:Path = [Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [Environment]::GetEnvironmentVariable("Path", "User") + Update-EnvironmentPath Write-StyledMessage -Type Success -Text "Git installato con successo." return $true } @@ -698,6 +765,10 @@ function Install-GitPackage { } function Format-CenteredText { + <# + .SYNOPSIS + Formatta un testo centrato rispetto alla larghezza specificata. + #> param( [string]$Text, [int]$Width = 80 @@ -707,12 +778,16 @@ function Format-CenteredText { } function Show-Header { + <# + .SYNOPSIS + Visualizza l'header grafico dello script con titolo e versione. + #> param( [string]$Title, [string]$Version ) Clear-Host - $width = 65 + $width = $script:AppConfig.Layout.Width Write-Host ('═' * $width) -ForegroundColor Green @( ' __ __ _ _ _ ', @@ -729,15 +804,19 @@ function Show-Header { } function Write-StyledMessage { + <# + .SYNOPSIS + Scrive un messaggio formattato con timestamp, icona e colore, e lo salva nel log. + #> param( - [ValidateSet('Info', 'Warning', 'Error', 'Success')] + [ValidateSet('Info', 'Warning', 'Error', 'Success', 'Progress')] [string]$Type, [string]$Text ) # FIX: Windows 11 Indentation Issue if ([Environment]::OSVersion.Version.Build -ge 22000) { $Text = "`r$Text" } - $style = $Global:MsgStyles[$Type] + $style = $script:AppConfig.MsgStyles[$Type] $timestamp = Get-Date -Format "HH:mm:ss" Write-Host "[$timestamp] $($style.Icon) $Text" -ForegroundColor $style.Color @@ -777,8 +856,7 @@ function Start-ToolkitLog { [START LOG HEADER] Start time : $dateTime ToolName : $ToolName -Username : $([Environment]::UserDomainName + '\' + [Environment]::UserName) -Machine : $($env:COMPUTERNAME) ($($os.Caption) $($os.Version)) +OS : $($os.Caption) $($os.Version) PSVersion : $psVer ToolkitVersion : $($script:AppConfig.Header.Version) [END LOG HEADER] @@ -801,6 +879,8 @@ function Write-ToolkitLog { $ts = Get-Date -Format "HH:mm:ss" $clean = $Message -replace '^\s+', '' + # Rimuovi tutti i caratteri ANSI/colori prima di salvare su file + $clean = $clean -replace '\x1B\[[0-9;]*[a-zA-Z]', '' $line = "[$ts] [$Level] $clean" try { Add-Content -Path $Global:CurrentLogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue } catch {} } @@ -811,7 +891,7 @@ function Start-AppxSilentProcess { Installa AppX in background sopprimendo le barre di progresso native. #> param( - [string]$AppxPath, + [string]$AppxPath, [string]$Flags = '-ForceApplicationShutdown', [string[]]$DependencyPaths = @() ) @@ -821,14 +901,14 @@ function Start-AppxSilentProcess { if ($DependencyPaths.Count -gt 0) { $depString = "-DependencyPackagePath " + (($DependencyPaths | ForEach-Object { "'$($_ -replace "'", "''")'" }) -join ", ") } - + $cmd = @" `$ProgressPreference = 'SilentlyContinue'; `$ErrorActionPreference = 'SilentlyContinue'; -try { - Add-AppxPackage -Path '$($AppxPath -replace "'", "''")' $depString $Flags -ErrorAction Stop | Out-Null +try { + Add-AppxPackage -Path '$($AppxPath -replace "'", "''")' $depString $Flags -ErrorAction Stop | Out-Null } -catch { +catch { if (`$_.Exception.Message -match '0x80073D06' -or `$_.Exception.Message -match 'versione successiva') { exit 0 } @@ -841,7 +921,7 @@ catch { `$_.Exception.Message | Out-File '$errFile' -Encoding UTF8; exit 1 } } - `$_.Exception.Message | Out-File '$errFile' -Encoding UTF8; exit 1 + `$_.Exception.Message | Out-File '$errFile' -Encoding UTF8; exit 1 } exit 0 "@ @@ -867,24 +947,13 @@ exit 0 return $true } -function Stop-InterferingProcess { - # Lista mirata dei processi che bloccano effettivamente l'installazione Appx - $interferingProcesses = @( - "WinStore.App", - "wsappx", - "AppInstaller", - "Microsoft.WindowsStore", - "Microsoft.DesktopAppInstaller", - "winget", - "WindowsPackageManagerServer" - ) - foreach ($procName in $interferingProcesses) { - $null = Get-Process -Name $procName -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue - } - Start-Sleep 2 -} + function Update-EnvironmentPath { + <# + .SYNOPSIS + Ricarica le variabili PATH di sistema e utente nella sessione corrente. + #> # Ricarica PATH da Machine e User per rilevare installazioni avvenute nel processo corrente $machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine') $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') @@ -896,7 +965,40 @@ function Update-EnvironmentPath { [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'Process') } +function Invoke-DownloadFile { + <# + .SYNOPSIS + Helper DRY per download file con gestione errori centralizzata. + #> + param( + [string]$Uri, + [string]$OutFile, + [switch]$Silent + ) + + try { + $iwrParams = @{ + Uri = $Uri + OutFile = $OutFile + UseBasicParsing = $true + ErrorAction = 'Stop' + } + Invoke-WebRequest @iwrParams + return $true + } + catch { + if (-not $Silent) { + Write-StyledMessage -Type Warning -Text "Errore download: $($_.Exception.Message)" + } + return $false + } +} + function Add-ToEnvironmentPath { + <# + .SYNOPSIS + Aggiunge un percorso alla variabile d'ambiente PATH nello scope specificato. + #> param ( [Parameter(Mandatory = $true)] [string]$PathToAdd, @@ -926,22 +1028,32 @@ function Add-ToEnvironmentPath { } function Invoke-WingetCommand { + <# + .SYNOPSIS + Esegue un comando Winget con gestione della compatibilità tra versioni. + #> param( [string]$Arguments, [int]$TimeoutSeconds = 120 ) try { + $wingetExe = Get-WinGetExecutable + if (-not $wingetExe) { + Write-StyledMessage -Type Warning -Text "Winget non trovato nel sistema." + return @{ ExitCode = -1 } + } + # Verifichiamo la versione di winget per retrocompatibilità # --disable-interactivity è supportato dalla versione 1.4+ - $versionRaw = (winget --version 2>$null) | Out-String + $versionRaw = (& $wingetExe --version 2>$null) | Out-String $isModern = $versionRaw -match 'v1\.[4-9]' -or $versionRaw -match 'v[2-9]' # Aggiungiamo il flag solo se supportato (v1.4+) $finalArgs = if ($isModern) { "$Arguments --disable-interactivity" } else { $Arguments } $procParams = @{ - FilePath = 'winget' + FilePath = $wingetExe ArgumentList = $finalArgs -split ' ' Wait = $true PassThru = $true @@ -957,6 +1069,10 @@ function Invoke-WingetCommand { } function Test-PathInEnvironment { + <# + .SYNOPSIS + Verifica se un percorso è presente nella variabile PATH dell'ambiente specificato. + #> param ( [string]$PathToCheck, [string]$Scope = 'Both' @@ -965,7 +1081,7 @@ function Test-PathInEnvironment { $pathExists = $false if ($Scope -eq 'User' -or $Scope -eq 'Both') { - $userEnvPath = $env:PATH + $userEnvPath = [Environment]::GetEnvironmentVariable('PATH', [EnvironmentVariableTarget]::User) if (($userEnvPath -split ';').Contains($PathToCheck)) { $pathExists = $true } @@ -1010,19 +1126,13 @@ function Set-PathPermissions { } } -# Test-WingetCompatibility and Test-WingetFunctionality moved to lib/ - -# ============================================================================ -# FUNZIONI DI RIPARAZIONE WINGET (Moved to lib/) -# ============================================================================ - - -# ============================================================================ -# FUNZIONI DI INSTALLAZIONE (Moved to lib/) -# ============================================================================ function Install-PowerShellCore { + <# + .SYNOPSIS + Verifica e installa PowerShell 7 con fallback a download diretto. + #> Write-StyledMessage -Type Info -Text "Verifica PowerShell 7." $ps7Path64 = "$env:SystemDrive\Program Files\PowerShell\7" @@ -1111,6 +1221,10 @@ function Install-PowerShellCore { } function Install-WindowsTerminalApp { + <# + .SYNOPSIS + Verifica e installa Windows Terminal con diversi metodi di fallback. + #> Write-StyledMessage -Type Info -Text "Configurazione Windows Terminal." if (Get-Command "wt.exe" -ErrorAction SilentlyContinue) { @@ -1119,7 +1233,6 @@ function Install-WindowsTerminalApp { } Write-StyledMessage -Type Info -Text "Installazione Windows Terminal in corso." - $downloadUrl = $null try { $winget = Get-Command winget -ErrorAction SilentlyContinue if ($winget) { @@ -1161,7 +1274,6 @@ function Install-WindowsTerminalApp { throw "Installazione Appx di Windows Terminal fallita." } $null = Remove-Item $tempFile -Force -ErrorAction SilentlyContinue - Write-StyledMessage -Type Success -Text "Installazione Appx di Windows Terminal riuscita." return $true } catch { @@ -1178,53 +1290,61 @@ function Install-WindowsTerminalApp { return $false } -function Install-PspEnvironment { - Write-StyledMessage -Type Info -Text "Avvio configurazione ambiente PowerShell (PSP)." - - # ============================================================================ - # HELPER FUNCTIONS LOCALI - # ============================================================================ - - function Install-NerdFontsLocal { - try { - Write-StyledMessage -Type Info -Text "🔍 Verifica presenza JetBrainsMono Nerd Font." +function Install-NerdFontsLocal { + <# + .SYNOPSIS + Verifica e installa JetBrainsMono Nerd Font tramite Winget. + #> + try { + Write-StyledMessage -Type Info -Text "🔍 Verifica presenza JetBrainsMono Nerd Font." - # Controllo rapido se il font è già registrato nel sistema - $fontRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" - $installed = Get-ItemProperty -Path $fontRegistryPath -ErrorAction SilentlyContinue | - Get-Member -MemberType NoteProperty | - Where-Object Name -like "*JetBrainsMono*" + # Controllo rapido se il font è già registrato nel sistema + $fontRegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" + $installed = Get-ItemProperty -Path $fontRegistryPath -ErrorAction SilentlyContinue | + Get-Member -MemberType NoteProperty | + Where-Object Name -like "*JetBrainsMono*" - if ($installed) { - Write-StyledMessage -Type Success -Text "✅ JetBrainsMono Nerd Font già installato." - return $true - } + if ($installed) { + Write-StyledMessage -Type Success -Text "✅ JetBrainsMono Nerd Font già installato." + return $true + } - Write-StyledMessage -Type Info -Text "⬇️ Installazione font tramite WinGet (Metodo Rapido)." + Write-StyledMessage -Type Info -Text "⬇️ Installazione font tramite WinGet (Metodo Rapido)." - # Utilizzo della funzione helper esistente per coerenza logica - $result = Invoke-WingetCommand -Arguments "install --id DEVCOM.JetBrainsMonoNerdFont --source winget --accept-source-agreements --accept-package-agreements --silent" + # Utilizzo della funzione helper esistente per coerenza logica + $result = Invoke-WingetCommand -Arguments "install --id DEVCOM.JetBrainsMonoNerdFont --source winget --accept-source-agreements --accept-package-agreements --silent" - if ($result.ExitCode -ne 0) { - Write-StyledMessage -Type Warning -Text "⚠️ WinGet ha restituito codice $($result.ExitCode). Il font potrebbe richiedere un riavvio del terminale." - return $false - } - Write-StyledMessage -Type Success -Text "✅ Nerd Fonts installati con successo." - Write-StyledMessage -Type Warning -Text "💡 Nota: i font via WinGet richiedono il riavvio del Terminale (o di Explorer) per essere visibili." - return $true - } - catch { - Write-StyledMessage -Type Warning -Text "Errore durante l'installazione font: $($_.Exception.Message)." + if ($result.ExitCode -ne 0) { + Write-StyledMessage -Type Warning -Text "⚠️ WinGet ha restituito codice $($result.ExitCode). Il font potrebbe richiedere un riavvio del terminale." return $false } + Write-StyledMessage -Type Success -Text "✅ Nerd Fonts installati con successo." + Write-StyledMessage -Type Warning -Text "💡 Nota: i font via WinGet richiedono il riavvio del Terminale (o di Explorer) per essere visibili." + return $true + } + catch { + Write-StyledMessage -Type Warning -Text "Errore durante l'installazione font: $($_.Exception.Message)." + return $false } +} - function Get-ProfileDirLocal { - if ($PSVersionTable.PSEdition -eq "Core") { - return [Environment]::GetFolderPath("MyDocuments") + "\PowerShell" - } - return [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell" +function Get-ProfileDirLocal { + <# + .SYNOPSIS + Restituisce il percorso della cartella profilo PowerShell corretta per l'edizione corrente. + #> + if ($PSVersionTable.PSEdition -eq "Core") { + return [Environment]::GetFolderPath("MyDocuments") + "\PowerShell" } + return [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell" +} + +function Install-PspEnvironment { + <# + .SYNOPSIS + Configura l'ambiente PowerShell con tool, temi e profilo personalizzato. + #> + Write-StyledMessage -Type Info -Text "Avvio configurazione ambiente PowerShell (PSP)." # ============================================================================ # ESECUZIONE SETUP PSP @@ -1246,70 +1366,49 @@ function Install-PspEnvironment { } # 2. Installazione Tema Oh My Posh - $profileDir = Get-ProfileDirLocal - if ($profileDir) { - $themesFolder = Join-Path $profileDir "Themes" - if (-not (Test-Path $themesFolder)) { - New-Item -Path $themesFolder -ItemType Directory -Force *>$null - } + # Sempre nella cartella PowerShell 7 (il profilo è specifico per PS7 e Windows Terminal) + $ps7ProfileDir = [Environment]::GetFolderPath('MyDocuments') + '\PowerShell' + $themesFolder = Join-Path $ps7ProfileDir 'Themes' + if (-not (Test-Path $themesFolder)) { + New-Item -Path $themesFolder -ItemType Directory -Force *>$null + } - $themePath = Join-Path $themesFolder "atomic.omp.json" - try { - $iwrParams = @{ - Uri = $script:AppConfig.URLs.OhMyPoshTheme - OutFile = $themePath - UseBasicParsing = $true - } - Invoke-WebRequest @iwrParams - Write-StyledMessage -Type Success -Text "Tema Oh My Posh scaricato." - } - catch { - Write-StyledMessage -Type Warning -Text "Errore download tema: $($_.Exception.Message)." - } + $themePath = Join-Path $themesFolder 'atomic.omp.json' + if (Invoke-DownloadFile -Uri $script:AppConfig.URLs.OhMyPoshTheme -OutFile $themePath) { + Write-StyledMessage -Type Success -Text 'Tema Oh My Posh scaricato.' } # 3. Installazione Font Install-NerdFontsLocal *>$null - # 4. Configurazione Profilo - if ($profileDir) { - if (-not (Test-Path $profileDir)) { - New-Item -Path $profileDir -ItemType Directory -Force *>$null - } - $targetProfile = $PROFILE - if (-not $targetProfile) { - $targetProfile = Join-Path $profileDir "Microsoft.PowerShell_profile.ps1" - } - try { - if (Test-Path $targetProfile) { - Move-Item -Path $targetProfile -Destination "$targetProfile.bak" -Force -ErrorAction SilentlyContinue - } - $iwrParams = @{ - Uri = $script:AppConfig.URLs.PowerShellProfile - OutFile = $targetProfile - UseBasicParsing = $true - } - Invoke-WebRequest @iwrParams - Write-StyledMessage -Type Success -Text "Profilo PowerShell configurato." + # 4. Configurazione Profilo (sempre nella cartella PowerShell 7) + if (-not (Test-Path $ps7ProfileDir)) { + New-Item -Path $ps7ProfileDir -ItemType Directory -Force *>$null + } + $targetProfile = Join-Path $ps7ProfileDir 'Microsoft.PowerShell_profile.ps1' + try { + if (Test-Path $targetProfile) { + Move-Item -Path $targetProfile -Destination "$targetProfile.bak" -Force -ErrorAction SilentlyContinue } - catch { - Write-StyledMessage -Type Warning -Text "Errore configurazione profilo: $($_.Exception.Message)." + if (Invoke-DownloadFile -Uri $script:AppConfig.URLs.PowerShellProfile -OutFile $targetProfile) { + Write-StyledMessage -Type Success -Text 'Profilo PowerShell 7 configurato.' } } + catch { + Write-StyledMessage -Type Warning -Text "Errore configurazione profilo: $($_.Exception.Message)." + } - # 5. Configurazione Settings Windows Terminal + # 5. Configurazione Settings Windows Terminal (stable e preview) try { - $wtPath = Get-ChildItem -Path "$env:LOCALAPPDATA\Packages" -Directory -Filter "Microsoft.WindowsTerminal_*" -ErrorAction SilentlyContinue | Select-Object -First 1 - if ($wtPath) { - $settingsPath = Join-Path $wtPath.FullName "LocalState\settings.json" - if (Test-Path (Join-Path $wtPath.FullName "LocalState")) { - $iwrParams = @{ - Uri = $script:AppConfig.URLs.WindowsTerminalSettings - OutFile = $settingsPath - UseBasicParsing = $true + $wtPackages = Get-ChildItem -Path "$env:LOCALAPPDATA\Packages" -Directory ` + -Filter 'Microsoft.WindowsTerminal*' -ErrorAction SilentlyContinue + foreach ($wtPkg in $wtPackages) { + $localStatePath = Join-Path $wtPkg.FullName 'LocalState' + if (Test-Path $localStatePath) { + $settingsPath = Join-Path $localStatePath 'settings.json' + if (Invoke-DownloadFile -Uri $script:AppConfig.URLs.WindowsTerminalSettings -OutFile $settingsPath) { + Write-StyledMessage -Type Success -Text "Settings Windows Terminal aggiornati ($($wtPkg.Name))." } - Invoke-WebRequest @iwrParams - Write-StyledMessage -Type Success -Text "Settings Windows Terminal aggiornati." } } } @@ -1338,12 +1437,7 @@ function New-ToolkitDesktopShortcut { if (-not (Test-Path $icon)) { Write-StyledMessage -Type Info -Text "Download icona." - $iwrParams = @{ - Uri = $script:AppConfig.URLs.ToolkitIcon - OutFile = $icon - UseBasicParsing = $true - } - Invoke-WebRequest @iwrParams + Invoke-DownloadFile -Uri $script:AppConfig.URLs.ToolkitIcon -OutFile $icon } $shell = New-Object -ComObject WScript.Shell @@ -1372,8 +1466,12 @@ function New-ToolkitDesktopShortcut { # ============================================================================ function Test-SystemReadiness { + <# + .SYNOPSIS + Esegue i controlli pre-flight sull'ambiente di sistema. + #> Write-StyledMessage -Type Info -Text "Esecuzione controlli di integrità sistema..." - + # 1. Verifica Windows Defender $defenderReady = $false try { @@ -1381,14 +1479,15 @@ function Test-SystemReadiness { if ($null -eq $status -or $status.RealTimeProtectionEnabled -eq $false) { $defenderReady = $true } - } catch { + } + catch { $defenderReady = $true # Se non può leggere lo stato, assumiamo sia spento o rimosso } # 2. Verifica Windows Update (Aggiornamenti pendenti) $updatesReady = $false try { - Write-StyledMessage -Type Progress -Text "Controllo Windows Update (Scansione locale)..." + Write-StyledMessage -Type Info -Text "Controllo Windows Update (Scansione locale)..." $session = New-Object -ComObject Microsoft.Update.Session $searcher = $session.CreateUpdateSearcher() $searcher.Online = $false # Impedisce la ricerca in rete che causa il blocco @@ -1397,7 +1496,8 @@ function Test-SystemReadiness { if ($result.Updates.Count -eq 0) { $updatesReady = $true } - } catch { + } + catch { $updatesReady = $true # Fallback se il servizio update è bloccato } @@ -1409,33 +1509,37 @@ function Test-SystemReadiness { } function Invoke-WinToolkitSetup { - param( - [switch]$InstallProfileOnly - ) + <# + .SYNOPSIS + Funzione principale che orchestra l'intero processo di installazione e configurazione di WinToolkit. + #> + [CmdletBinding()] + param() try { - $isResumeSetup = $env:WINTOOLKIT_RESUME -eq "1" $Host.UI.RawUI.WindowTitle = "Toolkit Starter by MagnetarMan" # Inizializza Logging Start-ToolkitLog "WinToolkitStarter" - # FIX: Correzione Sintassi ForEach-Object e Join (Aggiunte parentesi) + # Costruzione argomenti per riavvio $argList = ($PSBoundParameters.GetEnumerator() | ForEach-Object { if ($_.Value -is [switch] -and $_.Value) { "-$($_.Key)" } elseif ($_.Value -is [array]) { "-$($_.Key) $($_.Value -join ',')" } elseif ($_.Value) { "-$($_.Key) '$($_.Value)'" } - }) -join ' ' + } | Where-Object { $_ }) -join ' ' $startUrl = $script:AppConfig.URLs.StartScript + + # Blocco di riavvio standard $scriptBlockForRelaunch = if ($PSCommandPath) { "& '$PSCommandPath' $argList" } else { - "iex (irm '$startUrl') $argList" + "`$s = irm '$startUrl'; & ([scriptblock]::Create(`$s)) $argList" } - if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-StyledMessage -Type Info -Text "Riavvio con privilegi amministratore." $procParams = @{ FilePath = 'powershell' @@ -1443,111 +1547,101 @@ function Invoke-WinToolkitSetup { Verb = 'RunAs' } Start-Process @procParams - return + exit } - # --- INIZIO PRE-FLIGHT CHECK --- + # --- PRE-FLIGHT CHECK --- while ($true) { Show-Header -Title $script:AppConfig.Header.Title -Version $script:AppConfig.Header.Version $check = Test-SystemReadiness - if ($check.Defender -and $check.Updates) { - Write-StyledMessage -Type Success -Text "Ambiente pronto per l'installazione." - break # Esci dal loop e prosegui lo script - } - - Write-Host "`n" + ("!" * 65) -ForegroundColor Yellow + # Windows Defender SEMPRE obbligatorio if (-not $check.Defender) { - Write-StyledMessage -Type Warning -Text "ATTENZIONE: Windows Defender è ATTIVO." + Write-Host "`n" + ("!" * $script:AppConfig.Layout.Width) -ForegroundColor Red + Write-StyledMessage -Type Error -Text "OBBLIGATORIO: Windows Defender è ATTIVO." Write-StyledMessage -Type Info -Text "Disabilita la protezione in tempo reale per evitare blocchi." + Write-Host ("!" * $script:AppConfig.Layout.Width) -ForegroundColor Red + + Write-Host "`n[Pressione tasto] Riprova i controlli" -ForegroundColor Cyan + Write-Host "[ESC] Esci dallo script" -ForegroundColor Red + + $key = [Console]::ReadKey($true) + if ($key.Key -eq 'Escape') { exit } + Clear-Host + continue } + + # Se Defender è ok, controlla aggiornamenti: solo avviso, prosegue automaticamente if (-not $check.Updates) { - Write-StyledMessage -Type Warning -Text "ATTENZIONE: Ci sono $($check.Count) aggiornamenti Windows pendenti." - Write-StyledMessage -Type Info -Text "Attendi il completamento degli aggiornamenti prima di proseguire." + Write-StyledMessage -Type Warning -Text "⚠️ Ci sono $($check.Count) aggiornamenti Windows pendenti. Possibili problemi durante installazione." } - Write-Host ("!" * 65) -ForegroundColor Yellow - - Write-Host "`n[Pressione tasto] Riprova i controlli" -ForegroundColor Cyan - Write-Host "[ESC] Esci dallo script" -ForegroundColor Red - - $key = [Console]::ReadKey($true) - if ($key.Key -eq 'Escape') { exit } - Clear-Host + + # Tutti i controlli superati + Write-StyledMessage -Type Success -Text "Ambiente pronto per l'installazione." + break } + + # Sospensione servizi Windows Update per garantire stabilità a Winget + Invoke-StopUpdateServices # --- FINE PRE-FLIGHT CHECK --- + Write-StyledMessage -Type Info -Text "PowerShell: $($PSVersionTable.PSVersion)." if ($PSVersionTable.PSVersion.Major -lt 7) { Write-StyledMessage -Type Warning -Text "PowerShell 7 raccomandato per funzionalità avanzate." } Write-StyledMessage -Type Info -Text "Avvio configurazione Win Toolkit." - $rebootNeeded = $false - if (-not $isResumeSetup) { - Write-StyledMessage -Type Info -Text "Esecuzione controlli base." + Write-StyledMessage -Type Info -Text "Esecuzione controlli base." - # Aggiorna PATH prima del check iniziale per rilevare winget già installato - Update-EnvironmentPath + # Aggiorna PATH prima del check iniziale per rilevare winget già installato + Update-EnvironmentPath - if (-not (Test-WingetFunctionality)) { - Write-StyledMessage -Type Warning -Text "⚠️ Winget non risponde. Tentativo di ripristino veloce (Core)." - $coreSuccess = Install-WingetCore - Update-EnvironmentPath + if (-not (Test-WingetFunctionality)) { + Write-StyledMessage -Type Warning -Text "⚠️ Winget non risponde. Tentativo di ripristino veloce (Core)." + $coreSuccess = Install-WingetCore + Update-EnvironmentPath - if ($coreSuccess -and (Test-WingetFunctionality)) { - Write-StyledMessage -Type Success -Text "✅ Winget ripristinato velocemente." - } - else { - Write-StyledMessage -Type Warning -Text "⚠️ Ripristino veloce fallito. Tentativo metodo avanzato (più lento)." - $null = Install-WingetPackage - Update-EnvironmentPath - - if (-not (Test-WingetFunctionality)) { - Write-StyledMessage -Type Warning -Text "⚠️ Winget non funzionale dopo tutti i tentativi." - Write-StyledMessage -Type Info -Text "Lo script proseguirà, ma l'installazione di pacchetti potrebbe fallire." - } - } + if ($coreSuccess -and (Test-WingetFunctionality)) { + Write-StyledMessage -Type Success -Text "✅ Winget ripristinato velocemente." } else { - Write-StyledMessage -Type Success -Text "✅ Winget è già operativo." - } - - # Validazione profonda - $wingetDeepCheck = Test-WingetDeepValidation - if (-not $wingetDeepCheck) { - Write-StyledMessage -Type Warning -Text "⚠️ Attenzione: l'installazione dei pacchetti successivi via Winget potrebbe fallire." - } - - $gitInstalled = Install-GitPackage + Write-StyledMessage -Type Warning -Text "⚠️ Ripristino veloce fallito. Tentativo metodo avanzato (più lento)." + $null = Install-WingetPackage + Update-EnvironmentPath - # Controllo rapido che non richieda chiamate e garantisca fallback veloce - if (-not (Test-Path "$env:ProgramFiles\PowerShell\7") -and -not (Test-Path "${env:ProgramFiles(x86)}\PowerShell\7") -and -not (Get-Command pwsh -ErrorAction SilentlyContinue)) { - Install-PowerShellCore - } - else { - Write-StyledMessage -Type Success -Text "PowerShell 7 già presente." + if (-not (Test-WingetFunctionality)) { + Write-StyledMessage -Type Warning -Text "⚠️ Winget non funzionale dopo tutti i tentativi." + Write-StyledMessage -Type Info -Text "Lo script proseguirà, ma l'installazione di pacchetti potrebbe fallire." + } } } + else { + Write-StyledMessage -Type Success -Text "✅ Winget è già operativo." + } - $pwshExe64 = "$env:SystemDrive\Program Files\PowerShell\7\pwsh.exe" - $pwshExe32 = "$env:SystemDrive\Program Files (x86)\PowerShell\7\pwsh.exe" - $pwshExe = if (Test-Path $pwshExe64) { $pwshExe64 } elseif (Test-Path $pwshExe32) { $pwshExe32 } else { $null } + # Verifica in modo approfondito che Winget funzioni correttamente. + if (-not $(Test-WingetDeepValidation)) { + Write-StyledMessage -Type Warning -Text "⚠️ Attenzione: l'installazione dei pacchetti successivi via Winget potrebbe fallire." + } - if ($PSVersionTable.PSVersion.Major -lt 7 -and $pwshExe) { - Write-StyledMessage -Type Info -Text "✨ Rilevata PowerShell 7. Upgrade dell'ambiente di esecuzione." - Start-Sleep 2 - $env:WINTOOLKIT_RESUME = "1" + # Installa Git + if (Install-GitPackage) { + Write-StyledMessage -Type Success -Text "✅ Git è già operativo." + } + else { + Write-StyledMessage -Type Warning -Text "⚠️ Attenzione: Git non è stato installato oppure potrebbe non funzionare correttamente." + } - $procParams = @{ - FilePath = $pwshExe - ArgumentList = @("-ExecutionPolicy", "Bypass", "-NoExit", "-Command", "`"$scriptBlockForRelaunch`"") - Verb = "RunAs" - } - Start-Process @procParams - Write-StyledMessage -Type Success -Text "Script riavviato su PowerShell 7. Chiusura sessione legacy." - exit + # Controllo e installazione PowerShell 7 + if (-not (Test-Path "$env:ProgramFiles\PowerShell\7") -and -not (Test-Path "${env:ProgramFiles(x86)}\PowerShell\7") -and -not (Get-Command pwsh -ErrorAction SilentlyContinue)) { + Install-PowerShellCore + } + else { + Write-StyledMessage -Type Success -Text "PowerShell 7 già presente." } - # Installazioni core + + # Installazioni core Windows Terminal $wtInstalled = Install-WindowsTerminalApp # Imposta Windows Terminal come terminale predefinito @@ -1557,11 +1651,9 @@ function Invoke-WinToolkitSetup { try { $registryPath = $script:AppConfig.Registry.TerminalStartup if (-not (Test-Path $registryPath)) { $null = New-Item -Path $registryPath -Force } - $wtClsid = '{E12F0936-0E6F-548E-A9F6-B20C69A27D17}' - $consoleHostClsid = '{B23D10C0-31E3-401A-97EF-4BB30B62E10B}' - Set-ItemProperty -Path $registryPath -Name 'DelegationTerminal' -Value $wtClsid -Force - Set-ItemProperty -Path $registryPath -Name 'DelegationConsole' -Value $consoleHostClsid -Force + Set-ItemProperty -Path $registryPath -Name 'DelegationTerminal' -Value $script:AppConfig.WindowsTerminal.DelegationTerminalClsid -Force + Set-ItemProperty -Path $registryPath -Name 'DelegationConsole' -Value $script:AppConfig.WindowsTerminal.DelegationConsoleClsid -Force Write-StyledMessage -Type Success -Text "✅ Windows Terminal impostato come predefinito." } catch { @@ -1569,51 +1661,24 @@ function Invoke-WinToolkitSetup { } } + # SEMPRE eseguito: Installazione ambiente PSP e profilo Install-PspEnvironment + New-ToolkitDesktopShortcut - Write-StyledMessage -Type Success -Text "Configurazione completata." - - if ($isResumeSetup) { - Write-StyledMessage -Type Info -Text "Installazione ripresa, sessione completata." - return - } - - $canLaunchWT = (Get-Command "wt.exe" -ErrorAction SilentlyContinue) - if (-not ($env:WT_SESSION) -and $canLaunchWT) { - Write-StyledMessage -Type Info -Text "Riavvio dello script in Windows Terminal." - $pwshExe64 = "$env:SystemDrive\Program Files\PowerShell\7\pwsh.exe" - $pwshExe32 = "$env:SystemDrive\Program Files (x86)\PowerShell\7\pwsh.exe" - $pwshPath = if (Test-Path $pwshExe64) { $pwshExe64 } elseif (Test-Path $pwshExe32) { $pwshExe32 } else { "powershell.exe" } - - $wtArgs = "-w 0 new-tab -p `"PowerShell`" -d . `"$pwshPath`" -ExecutionPolicy Bypass -NoExit -Command `"$scriptBlockForRelaunch`"" + # Ripristino servizi in caso di successo + Invoke-StartUpdateServices - try { - Start-Process -FilePath "wt.exe" -ArgumentList $wtArgs - Write-StyledMessage -Type Success -Text "Script riavviato in Windows Terminal. Chiusura sessione." - exit - } - catch { - Write-StyledMessage -Type Error -Text "Errore avvio Windows Terminal: $($_.Exception.Message)." - } - } - - if (-not ($env:WT_SESSION) -and -not $canLaunchWT) { - Write-StyledMessage -Type Warning -Text "L'installazione è stata comunque completata nella console corrente." - } + Write-StyledMessage -Type Success -Text "Configurazione completata." - if ($rebootNeeded) { - Write-StyledMessage -Type Warning -Text "Riavvio necessario tra 10 secondi." - Start-Sleep 10 - Restart-Computer -Force - } - else { - Write-StyledMessage -Type Success -Text "WinToolkit è Pronto sul Desktop! 🚀" - Start-Sleep 3 - exit - } + Write-StyledMessage -Type Success -Text "WinToolkit è Pronto sul Desktop! 🚀" + Start-Sleep 3 + exit } catch { + # Ripristino servizi in caso di errore + Invoke-StartUpdateServices + Write-StyledMessage -Type Error -Text "❌ Errore critico durante il setup: $($_.Exception.Message)." Write-ToolkitLog -Level 'ERROR' -Message "ECCEZIONE UNHANDLED: $($_.Exception.Message) `n $($_.ScriptStackTrace)" Write-Host "Premi un tasto per uscire."