code.pablomurad.com

fast-scripts

28 double-click Windows batch tools for video, audio and files

More windows programs
author
Pablo Murad
date
commit
d6673f39b7a6c93b749aa800bbef82d5fc2362c1
parent
91ad877b609c1970114b901276fa63385f9c37cc
changes
10 files +2351 −177
@@ -1,50 +1,55 @@-# Sistema / editor
-Thumbs.db
-Desktop.ini
-.DS_Store
-*.swp
-*.tmp
-*~
-.vscode/
-.idea/
-
-# Segredos e ambiente (por precaução)
-.env
-.env.*
-*.pem
-*.key
-*.pfx
-credentials.json
-secrets.*
-
-# Saídas dos scripts (se rodarem dentro do repo)
-Convertido/
-Transcodificado/
-
-# Mídia que possa cair na pasta por acidente
-*.jpg
-*.jpeg
-*.png
-*.webp
-*.bmp
-*.gif
-*.tif
-*.tiff
-*.heic
-*.heif
-*.avif
-*.mp4
-*.mkv
-*.avi
-*.mov
-*.wmv
-*.flv
-*.webm
-*.m4v
-*.mpg
-*.mpeg
-*.mts
-*.m2ts
-*.ts
-*.3gp
-*.vob
+# Sistema / editor
+Thumbs.db
+Desktop.ini
+.DS_Store
+*.swp
+*.tmp
+*~
+.vscode/
+.idea/
+
+# Segredos e ambiente (por precaução)
+.env
+.env.*
+*.pem
+*.key
+*.pfx
+credentials.json
+secrets.*
+
+# Saídas dos scripts (se rodarem dentro do repo)
+Convertido/
+Transcodificado/
+
+# Mídia que possa cair na pasta por acidente
+*.jpg
+*.jpeg
+*.png
+*.webp
+*.bmp
+*.gif
+*.tif
+*.tiff
+*.heic
+*.heif
+*.avif
+*.mp4
+*.mkv
+*.avi
+*.mov
+*.wmv
+*.flv
+*.webm
+*.m4v
+*.mpg
+*.mpeg
+*.mts
+*.m2ts
+*.ts
+*.3gp
+*.vob
+
+# Saída do programa de drivers (se apontarem para dentro do repo)
+Drivers/
+*.cab
+*.inf
@@ -1,95 +1,102 @@-# fast-scripts
-
-Programas rápidos para Windows, em lote e com interface no terminal.
-**26 programas** que você abre com um duplo-clique — sem instalar nada, sem linha de comando.
-
-![PowerShell](https://img.shields.io/badge/PowerShell-5391FE?style=flat&logo=powershell&logoColor=white)
-![Batch](https://img.shields.io/badge/Batch-.cmd-4D4D4D?style=flat)
-![Windows](https://img.shields.io/badge/Windows-0078D6?style=flat&logo=windows&logoColor=white)
-
-## Começando
-
-Abra o **`canivete.cmd`** na raiz: ele lista todos os programas e abre o que você escolher.
-Ou vá direto na pasta e dê um duplo-clique no programa que quiser.
-
-Todos seguem a mesma lógica: perguntam a **pasta ou o arquivo**, as **opções**, e a **pasta de saída** — lembrando suas escolhas da próxima vez e criando a pasta se ela não existir. Nada destrutivo acontece sem prévia e confirmação.
-
-## Os programas
-
-### 🎬 Vídeos
-
-| Programa | O que faz |
-|---|---|
-| [transcodificar](docs/transcodificar.md) | Converte vídeos em lote, com GPU e 5 níveis de qualidade |
-| [baixar-videos](docs/baixar-videos.md) | Baixa vídeos e áudio de mais de mil sites (yt-dlp) |
-| [extrair-audio](docs/extrair-audio.md) | Tira o áudio de vídeos (MP3/M4A/WAV) |
-| [cortar-video](docs/cortar-video.md) | Corta um trecho do vídeo — instantâneo |
-| [juntar-videos](docs/juntar-videos.md) | Junta vários vídeos num só |
-| [criar-gif](docs/criar-gif.md) | Vídeo vira GIF, ou fotos viram slideshow |
-| [marca-dagua-video](docs/marca-dagua-video.md) | Marca d'água de texto ou logo em vídeos |
-| [normalizar-audio](docs/normalizar-audio.md) | Padroniza o volume (EBU R128) |
-| [info-midia](docs/info-midia.md) | Relatório de codec, resolução e bitrate + planilha |
-
-### 🖼️ Imagens
-
-| Programa | O que faz |
-|---|---|
-| [converter-imagens](docs/converter-imagens.md) | Redimensiona e converte imagens em lote |
-| [marca-dagua](docs/marca-dagua.md) | Marca d'água de texto ou logo em fotos |
-| [imagens-e-pdf](docs/imagens-e-pdf.md) | Junta imagens num PDF ou explode PDF em imagens |
-| [comprimir-pdf](docs/comprimir-pdf.md) | Reduz o tamanho de PDFs |
-
-### 🗂️ Organizar
-
-| Programa | O que faz |
-|---|---|
-| [renomear-em-lote](docs/renomear-em-lote.md) | Renomeia por sequência, data ou localizar/substituir |
-| [achar-duplicados](docs/achar-duplicados.md) | Acha arquivos iguais por conteúdo e libera espaço |
-| [organizar-por-tipo](docs/organizar-por-tipo.md) | Separa a bagunça em subpastas por categoria |
-| [compactar-zip](docs/compactar-zip.md) | Compacta e extrai arquivos |
-| [limpar-metadados](docs/limpar-metadados.md) | Remove EXIF e GPS de fotos e vídeos |
-| [limpar-nomes](docs/limpar-nomes.md) | Deixa os nomes de arquivo seguros para web |
-
-### 🧹 Úteis
-
-| Programa | O que faz |
-|---|---|
-| [backup-espelhado](docs/backup-espelhado.md) | Backup incremental e rápido com robocopy |
-| [verificar-discos](docs/verificar-discos.md) | Saúde dos HDs e SSDs (SMART) |
-| [verificar-integridade](docs/verificar-integridade.md) | Detecta arquivos corrompidos silenciosamente |
-| [analisar-espaco](docs/analisar-espaco.md) | Descobre o que está enchendo o disco |
-| [limpeza-segura](docs/limpeza-segura.md) | Limpa temporários e lixeira, com prévia |
-| [relatorio-sistema](docs/relatorio-sistema.md) | Specs do PC, memória, discos e espaço |
-
-### 🔪 Raiz
-
-| Programa | O que faz |
-|---|---|
-| [canivete](docs/canivete.md) | Menu central que abre todos os outros |
-
-## Instalação
-
-Não tem instalação: é só copiar a pasta. O que alguns programas precisam:
-
-```
-winget install Gyan.FFmpeg                    # vídeo e áudio
-winget install ImageMagick.ImageMagick        # imagens
-winget install ArtifexSoftware.GhostScript    # PDF
-winget install 7zip.7zip                      # opcional: .rar e .7z
-winget install smartmontools                  # opcional: SMART avançado
-```
-
-O **yt-dlp se instala sozinho** quando necessário. Cada programa avisa na tela se faltar algo — veja a lista completa em **[docs/DEPENDENCIAS.md](docs/DEPENDENCIAS.md)**.
-
-## Velocidade
-
-Os programas que trabalham em lote processam **vários arquivos ao mesmo tempo**, ajustando o número de processos ao tipo de trabalho. Em imagens o ganho medido chega a **4,2×**. O `transcodificar` e o `marca-dagua-video` usam a **placa NVIDIA** quando ela existe, e o `extrair-audio` **copia o áudio sem recodificar** quando o formato já é o certo — nesse caso é quase instantâneo.
-
-## Para quem for mexer no código
-
-As regras técnicas da coleção (cabeçalho polyglot, codificação, paralelismo e as armadilhas já mapeadas) estão em **[docs/PADROES.md](docs/PADROES.md)**.
-
----
-
-**Pablo Murad** — pablomurad[at]pm[dot]me
+# fast-scripts
+
+Programas rápidos para Windows, em lote e com interface no terminal.
+**28 programas** que você abre com um duplo-clique — sem instalar nada, sem linha de comando.
+
+![PowerShell](https://img.shields.io/badge/PowerShell-5391FE?style=flat&logo=powershell&logoColor=white)
+![Batch](https://img.shields.io/badge/Batch-.cmd-4D4D4D?style=flat)
+![Windows](https://img.shields.io/badge/Windows-0078D6?style=flat&logo=windows&logoColor=white)
+
+## Começando
+
+Abra o **`canivete.cmd`** na raiz: ele lista todos os programas e abre o que você escolher.
+Ou vá direto na pasta e dê um duplo-clique no programa que quiser.
+
+Todos seguem a mesma lógica: perguntam a **pasta ou o arquivo**, as **opções**, e a **pasta de saída** — lembrando suas escolhas da próxima vez e criando a pasta se ela não existir. Nada destrutivo acontece sem prévia e confirmação.
+
+## Os programas
+
+### 🎬 Vídeos
+
+| Programa | O que faz |
+|---|---|
+| [transcodificar](docs/transcodificar.md) | Converte vídeos em lote, com GPU e 5 níveis de qualidade |
+| [baixar-videos](docs/baixar-videos.md) | Baixa vídeos e áudio de mais de mil sites (yt-dlp) |
+| [extrair-audio](docs/extrair-audio.md) | Tira o áudio de vídeos (MP3/M4A/WAV) |
+| [cortar-video](docs/cortar-video.md) | Corta um trecho do vídeo — instantâneo |
+| [juntar-videos](docs/juntar-videos.md) | Junta vários vídeos num só |
+| [criar-gif](docs/criar-gif.md) | Vídeo vira GIF, ou fotos viram slideshow |
+| [marca-dagua-video](docs/marca-dagua-video.md) | Marca d'água de texto ou logo em vídeos |
+| [normalizar-audio](docs/normalizar-audio.md) | Padroniza o volume (EBU R128) |
+| [info-midia](docs/info-midia.md) | Relatório de codec, resolução e bitrate + planilha |
+
+### 🖼️ Imagens
+
+| Programa | O que faz |
+|---|---|
+| [converter-imagens](docs/converter-imagens.md) | Redimensiona e converte imagens em lote |
+| [marca-dagua](docs/marca-dagua.md) | Marca d'água de texto ou logo em fotos |
+| [imagens-e-pdf](docs/imagens-e-pdf.md) | Junta imagens num PDF ou explode PDF em imagens |
+| [comprimir-pdf](docs/comprimir-pdf.md) | Reduz o tamanho de PDFs |
+
+### 🗂️ Organizar
+
+| Programa | O que faz |
+|---|---|
+| [renomear-em-lote](docs/renomear-em-lote.md) | Renomeia por sequência, data ou localizar/substituir |
+| [achar-duplicados](docs/achar-duplicados.md) | Acha arquivos iguais por conteúdo e libera espaço |
+| [organizar-por-tipo](docs/organizar-por-tipo.md) | Separa a bagunça em subpastas por categoria |
+| [compactar-zip](docs/compactar-zip.md) | Compacta e extrai arquivos |
+| [limpar-metadados](docs/limpar-metadados.md) | Remove EXIF e GPS de fotos e vídeos |
+| [limpar-nomes](docs/limpar-nomes.md) | Deixa os nomes de arquivo seguros para web |
+
+### 🧹 Úteis
+
+| Programa | O que faz |
+|---|---|
+| [backup-espelhado](docs/backup-espelhado.md) | Backup incremental e rápido com robocopy |
+| [verificar-discos](docs/verificar-discos.md) | Saúde dos HDs e SSDs (SMART) |
+| [verificar-integridade](docs/verificar-integridade.md) | Detecta arquivos corrompidos silenciosamente |
+| [analisar-espaco](docs/analisar-espaco.md) | Descobre o que está enchendo o disco |
+| [limpeza-segura](docs/limpeza-segura.md) | Limpa temporários e lixeira, com prévia |
+| [relatorio-sistema](docs/relatorio-sistema.md) | Specs do PC, memória, discos e espaço |
+
+### 🪟 Windows
+
+| Programa | O que faz |
+|---|---|
+| [01 - drivers](docs/drivers.md) | Salva, restaura e atualiza os drivers da máquina |
+| [02 - debloat](docs/debloat.md) | Tira o excesso do Windows, com prévia e desfazer |
+
+### 🔪 Raiz
+
+| Programa | O que faz |
+|---|---|
+| [canivete](docs/canivete.md) | Menu central que abre todos os outros |
+
+## Instalação
+
+Não tem instalação: é só copiar a pasta. O que alguns programas precisam:
+
+```
+winget install Gyan.FFmpeg                    # vídeo e áudio
+winget install ImageMagick.ImageMagick        # imagens
+winget install ArtifexSoftware.GhostScript    # PDF
+winget install 7zip.7zip                      # opcional: .rar e .7z
+winget install smartmontools                  # opcional: SMART avançado
+```
+
+O **yt-dlp se instala sozinho** quando necessário. Cada programa avisa na tela se faltar algo — veja a lista completa em **[docs/DEPENDENCIAS.md](docs/DEPENDENCIAS.md)**.
+
+## Velocidade
+
+Os programas que trabalham em lote processam **vários arquivos ao mesmo tempo**, ajustando o número de processos ao tipo de trabalho. Em imagens o ganho medido chega a **4,2×**. O `transcodificar` e o `marca-dagua-video` usam a **placa NVIDIA** quando ela existe, e o `extrair-audio` **copia o áudio sem recodificar** quando o formato já é o certo — nesse caso é quase instantâneo.
+
+## Para quem for mexer no código
+
+As regras técnicas da coleção (cabeçalho polyglot, codificação, paralelismo e as armadilhas já mapeadas) estão em **[docs/PADROES.md](docs/PADROES.md)**.
+
+---
+
+**Pablo Murad** — pablomurad[at]pm[dot]me
 *Desenvolvido por Pablo Murad - 2026*
@@ -78,6 +78,27 @@ & $ytdlp -U 2>$null | Out-Null     }
 }
 
+# O YouTube muda constantemente: yt-dlp antigo falha ou perde formatos.
+try {
+    $ver = "$(& $ytdlp --version 2>$null | Select-Object -First 1)".Trim()
+    $dv = [datetime]::MinValue
+    if ([datetime]::TryParseExact($ver, 'yyyy.MM.dd', [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None, [ref]$dv)) {
+        $dias = [int]((Get-Date) - $dv).TotalDays
+        if ($dias -gt 60) {
+            Write-Host ''
+            Write-Host ("[!] Seu yt-dlp e de {0} ({1} dias atras)." -f $ver, $dias) -ForegroundColor Yellow
+            Write-Host '    Versoes antigas costumam falhar no YouTube.' -ForegroundColor Yellow
+            $up = Read-Host '    Atualizar agora? (S/n)'
+            if ($up -notmatch '^(n|nao|no)$') {
+                Write-Host '    Atualizando...' -ForegroundColor Gray
+                & $ytdlp -U
+                Write-Host ''
+            }
+        } else {
+            Write-Host ("[OK] yt-dlp atualizado ({0})." -f $ver) -ForegroundColor Green
+        }
+    }
+} catch {}
 # ffmpeg (necessário p/ juntar vídeo+áudio de alta qualidade e converter áudio)
 $ffmpeg = Get-Command ffmpeg -ErrorAction SilentlyContinue
 $ffmpegDir = if ($ffmpeg) { Split-Path $ffmpeg.Source -Parent } else { $null }
@@ -162,12 +183,23 @@ $ytArgs += @('-o', (Join-Path $destino '%(title)s.%(ext)s')) $ytArgs += @('--embed-metadata','--embed-thumbnail')     # título, capa, etc.
 $ytArgs += @('--no-overwrites')                          # pula o que já foi baixado
 $ytArgs += @('--concurrent-fragments','8')               # baixa varios pedacos de uma vez
-# O aria2c baixa em muitas conexoes ao mesmo tempo: bem mais rapido quando existe.
-$aria = Get-Command aria2c -ErrorAction SilentlyContinue
-if ($aria) {
-    Write-Host '[OK] aria2c encontrado: download acelerado ligado.' -ForegroundColor Green
-    $ytArgs += @('--downloader','aria2c','--downloader-args','aria2c:-x16 -s16 -k1M')
-}               # download mais rápido
+
+# O aria2c acelera muito, mas quebra em algumas redes. Fica num array
+# separado para dar para tentar de novo SEM ele se falhar.
+# --disable-ipv6: sem isso ele falha com "socket error 10051" (rede
+# inalcancavel) em conexoes que nao tem rota IPv6.
+$ariaArgs = @()
+$ehYoutube = [bool](@($urls | Where-Object { $_ -match 'youtube\.com|youtu\.be' }).Count)
+if (Get-Command aria2c -ErrorAction SilentlyContinue) {
+    if ($ehYoutube) {
+        # O YouTube derruba baixadores multi-conexao (experimento SABR), entao
+        # o aria2c falha justamente la. Nos outros sites ele acelera muito.
+        Write-Host '[i] aria2c existe, mas nao e usado no YouTube (o site bloqueia). Usando o modo normal.' -ForegroundColor DarkGray
+    } else {
+        Write-Host '[OK] aria2c encontrado: download acelerado ligado.' -ForegroundColor Green
+        $ariaArgs = @('--downloader','aria2c','--downloader-args','aria2c:-x16 -s16 -k1M --disable-ipv6')
+    }
+}
 $ytArgs += @('--ignore-errors')                          # em lote, continua se um falhar
 $ytArgs += if ($playlist) { '--yes-playlist' } else { '--no-playlist' }
 if ($ffmpegDir) { $ytArgs += @('--ffmpeg-location', $ffmpegDir) }
@@ -182,8 +214,19 @@ Write-Host "Destino:   $destino" Write-Host "Links:     $($urls.Count)"
 Write-Host ''
 
-& $ytdlp @ytArgs @urls
+& $ytdlp @ytArgs @ariaArgs @urls
 $rc = $LASTEXITCODE
+
+# Se falhou COM o aria2c, tenta de novo SEM ele. O download nativo do
+# yt-dlp e' mais lento, mas funciona em qualquer rede.
+if ($rc -ne 0 -and $ariaArgs.Count -gt 0) {
+    Write-Host ''
+    Write-Host '[!] O download acelerado (aria2c) falhou nesta rede.' -ForegroundColor Yellow
+    Write-Host '    Tentando de novo no modo normal...' -ForegroundColor Yellow
+    Write-Host ''
+    & $ytdlp @ytArgs @urls
+    $rc = $LASTEXITCODE
+}
 
 Titulo 'Concluído'
 if ($rc -eq 0) { Write-Host '[OK] Download finalizado.' -ForegroundColor Green }
@@ -0,0 +1,998 @@+<# :
+@echo off
+set "SELF=%~f0"
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText($env:SELF,[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Drivers - backup, restauração e atualização de drivers
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Drivers' } catch {}
+
+# ------------------------------------------------------------
+# Auto-elevação: exportar e instalar driver exige administrador
+# ------------------------------------------------------------
+$souAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
+    ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $souAdmin) {
+    Write-Host 'Solicitando privilégios de administrador...' -ForegroundColor Yellow
+    try {
+        Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', "`"$env:SELF`"" -Verb RunAs | Out-Null
+    } catch {
+        Write-Host "[ERRO] Sem administrador não dá para mexer em driver: $($_.Exception.Message)" -ForegroundColor Red
+        Start-Sleep -Seconds 3
+    }
+    exit
+}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para voltar ao menu...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+function Tam($bytes) {
+    if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) }
+    elseif ($bytes -ge 1MB) { '{0:N1} MB' -f ($bytes/1MB) }
+    else { '{0:N0} KB' -f ($bytes/1KB) }
+}
+
+$configDir  = Join-Path $env:APPDATA 'Drivers'
+$configFile = Join-Path $configDir 'config.json'
+function Carregar-Config { if (Test-Path -LiteralPath $configFile) { try { return Get-Content -LiteralPath $configFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch {} }; return $null }
+function Salvar-Config($o) { try { New-Item -ItemType Directory -Path $configDir -Force | Out-Null; $o | ConvertTo-Json | Set-Content -LiteralPath $configFile -Encoding UTF8 } catch {} }
+
+# Pergunta a pasta de saida: sugere a ultima usada e cria se nao existir
+function Pedir-Saida($padrao) {
+    while ($true) {
+        Titulo 'Pasta dos drivers'
+        Write-Host "  $padrao" -ForegroundColor White
+        Write-Host 'Enter = usar esta pasta   |   ou cole/digite outra (pendrive, rede...)' -ForegroundColor DarkGray
+        $r = Read-Host 'Pasta'
+        $d = if ([string]::IsNullOrWhiteSpace($r)) { $padrao } else { $r.Trim().Trim('"') }
+        try { New-Item -ItemType Directory -Path $d -Force | Out-Null; return (Resolve-Path -LiteralPath $d).Path }
+        catch { Write-Host '[!] Não foi possível criar/usar essa pasta.' -ForegroundColor Yellow }
+    }
+}
+
+function Abrir($titulo) {
+    Titulo $titulo
+    Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+    Write-Host ''
+    Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
+}
+
+# Pergunta S/n; Enter aceita o padrao
+function Confirmar([string]$pergunta, [bool]$padraoSim) {
+    $dica = if ($padraoSim) { '(S/n)' } else { '(s/N)' }
+    $r = Read-Host "$pergunta $dica"
+    if ([string]::IsNullOrWhiteSpace($r)) { return $padraoSim }
+    return ($r.Trim() -match '^(?i)(s|sim|y|yes)$')
+}
+
+function Abrir-Url([string]$u) {
+    try { Start-Process $u | Out-Null; Write-Host "  Abrindo: $u" -ForegroundColor Gray }
+    catch { Write-Host "  Abra no navegador: $u" -ForegroundColor White }
+}
+
+# Pede uma pasta ja existente, sugerindo um padrao
+function Pedir-Pasta([string]$sugestao, [string]$dica) {
+    Write-Host "  $sugestao" -ForegroundColor White
+    Write-Host $dica -ForegroundColor DarkGray
+    $r = Read-Host 'Pasta com os drivers'
+    $d = if ([string]::IsNullOrWhiteSpace($r)) { $sugestao } else { $r.Trim().Trim('"') }
+    if (-not (Test-Path -LiteralPath $d -PathType Container)) {
+        Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow
+        return $null
+    }
+    return (Resolve-Path -LiteralPath $d).Path
+}
+
+# ------------------------------------------------------------
+# Identificação da máquina
+# ------------------------------------------------------------
+function Detectar-Maquina {
+    $fab = ''; $mod = ''; $placa = ''
+    try {
+        $cs = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
+        $fab = "$($cs.Manufacturer)".Trim()
+        $mod = "$($cs.Model)".Trim()
+    } catch { Write-Host "[!] Não consegui ler o fabricante: $($_.Exception.Message)" -ForegroundColor DarkGray }
+    try {
+        $bb = Get-CimInstance Win32_BaseBoard -ErrorAction Stop | Select-Object -First 1
+        $placa = ("{0} {1}" -f "$($bb.Manufacturer)".Trim(), "$($bb.Product)".Trim()).Trim()
+    } catch { Write-Host "[!] Não consegui ler a placa-mãe: $($_.Exception.Message)" -ForegroundColor DarkGray }
+
+    $marca = 'outro'
+    if     ($fab -match '(?i)dell')          { $marca = 'dell' }
+    elseif ($fab -match '(?i)hp|hewlett')    { $marca = 'hp' }
+    elseif ($fab -match '(?i)lenovo')        { $marca = 'lenovo' }
+    # Quando o "fabricante" e' um fabricante de placa-mae, a maquina e' montada
+    elseif ($fab -match '(?i)system manufacturer|to be filled|o\.e\.m\.|asrock|gigabyte|micro-star|^msi$|biostar|supermicro' -or $fab -eq '') { $marca = 'montado' }
+
+    if (-not $fab)   { $fab = '(desconhecido)' }
+    if (-not $mod)   { $mod = '(desconhecido)' }
+    if (-not $placa) { $placa = '(desconhecida)' }
+    return [pscustomobject]@{ Fabricante=$fab; Modelo=$mod; PlacaMae=$placa; Marca=$marca }
+}
+
+function Placas-Video {
+    $r = @()
+    try {
+        foreach ($v in @(Get-CimInstance Win32_VideoController -ErrorAction Stop)) {
+            $n = "$($v.Name)".Trim()
+            if (-not $n) { continue }
+            $tipo = if ($n -match '(?i)nvidia|geforce|quadro|rtx|gtx') { 'nvidia' }
+                    elseif ($n -match '(?i)radeon|\bamd\b')            { 'amd' }
+                    elseif ($n -match '(?i)intel')                     { 'intel' }
+                    else                                               { 'outro' }
+            $r += [pscustomobject]@{ Nome=$n; Tipo=$tipo; Versao="$($v.DriverVersion)" }
+        }
+    } catch { Write-Host "[!] Não consegui listar a placa de vídeo: $($_.Exception.Message)" -ForegroundColor DarkGray }
+    return $r
+}
+
+# ------------------------------------------------------------
+# Inventário de dispositivos
+# ------------------------------------------------------------
+$codigosProblema = @{
+    1  = 'não está configurado corretamente'
+    3  = 'driver corrompido ou sem memória'
+    10 = 'não consegue iniciar'
+    12 = 'não há recursos livres suficientes'
+    18 = 'precisa reinstalar o driver'
+    19 = 'registro do dispositivo com problema'
+    21 = 'o Windows está removendo o dispositivo'
+    22 = 'desativado'
+    24 = 'não está presente ou está com defeito'
+    28 = 'SEM DRIVER instalado'
+    31 = 'o Windows não conseguiu carregar o driver'
+    37 = 'o driver não inicializou'
+    39 = 'driver corrompido ou ausente'
+    43 = 'o Windows parou o dispositivo (erro relatado)'
+    45 = 'não está conectado agora'
+}
+
+# Reduz o Hardware ID ao pedaco que o Catalog casa bem
+function Hwid-Curto([string]$h) {
+    if ($h -match '(?i)(PCI\\VEN_[0-9A-F]{4}&DEV_[0-9A-F]{4})')                        { return $matches[1].ToUpperInvariant() }
+    if ($h -match '(?i)(USB\\VID_[0-9A-F]{4}&PID_[0-9A-F]{4})')                        { return $matches[1].ToUpperInvariant() }
+    if ($h -match '(?i)(HDAUDIO\\FUNC_[0-9A-F]+&VEN_[0-9A-F]{4}&DEV_[0-9A-F]{4})')     { return $matches[1].ToUpperInvariant() }
+    return $h
+}
+
+function Coletar-Dispositivos($problemas) {
+    $drv = @{}
+    try {
+        foreach ($d in @(Get-CimInstance Win32_PnPSignedDriver -ErrorAction Stop)) {
+            if ($d.DeviceID) { $drv["$($d.DeviceID)"] = $d }
+        }
+    } catch { Write-Host "[!] Não consegui ler as versões de driver: $($_.Exception.Message)" -ForegroundColor DarkGray }
+
+    try { $ents = @(Get-CimInstance Win32_PnPEntity -ErrorAction Stop) }
+    catch { Write-Host "[ERRO] Não consegui listar os dispositivos: $($_.Exception.Message)" -ForegroundColor Red; return @() }
+
+    $lista = @()
+    foreach ($e in $ents) {
+        # O WMI devolve algumas entradas sem nome: nao servem para nada aqui
+        if ([string]::IsNullOrWhiteSpace("$($e.Name)")) { continue }
+        $cod = 0
+        try { $cod = [int]$e.ConfigManagerErrorCode } catch { $cod = 0 }
+        $d  = $drv["$($e.DeviceID)"]
+        $hw = ''
+        if ($e.HardwareID -and @($e.HardwareID).Count -gt 0) { $hw = "$(@($e.HardwareID)[0])" }
+        $ver = if ($d -and $d.DriverVersion) { "$($d.DriverVersion)" } else { '-' }
+        $dat = '-'
+        if ($d -and $d.DriverDate) {
+            try { $dat = ([datetime]$d.DriverDate).ToString('yyyy-MM-dd') } catch { $dat = '-' }
+        }
+        $lista += [pscustomobject]@{
+            Nome       = "$($e.Name)"
+            Classe     = if ($d) { "$($d.DeviceClass)" } else { '' }
+            Fabricante = if ($d -and $d.DriverProviderName) { "$($d.DriverProviderName)" } else { "$($e.Manufacturer)" }
+            Versao     = $ver
+            Data       = $dat
+            HardwareID = $hw
+            Codigo     = $cod
+            Problema   = if ($cod -ne 0) { if ($problemas.ContainsKey($cod)) { $problemas[$cod] } else { "código $cod" } } else { '' }
+        }
+    }
+    return @($lista | Sort-Object Nome)
+}
+
+function Salvar-Inventario($disp, [string]$arquivo) {
+    try {
+        $disp | Select-Object Nome,Classe,Fabricante,Versao,Data,HardwareID,Codigo,Problema |
+            Export-Csv -LiteralPath $arquivo -NoTypeInformation -Encoding UTF8
+        Write-Host ''
+        Write-Host ("[OK] Inventário salvo: {0}" -f $arquivo) -ForegroundColor Green
+    } catch { Write-Host "[!] Não consegui salvar o inventário: $($_.Exception.Message)" -ForegroundColor Yellow }
+}
+
+# Devolve os dispositivos com defeito real (22 = desativado e 45 = desconectado nao sao)
+function Mostrar-Inventario($disp) {
+    $ruins = @($disp | Where-Object { $_.Codigo -ne 0 -and $_.Codigo -ne 22 -and $_.Codigo -ne 45 })
+    Write-Host ''
+    Write-Host ("Dispositivos encontrados: {0}" -f @($disp).Count) -ForegroundColor White
+    if ($ruins.Count -eq 0) {
+        Write-Host '[OK] Nenhum dispositivo com problema: está tudo com driver.' -ForegroundColor Green
+        return @()
+    }
+    Write-Host ''
+    Write-Host ("{0} dispositivo(s) precisando de atenção:" -f $ruins.Count) -ForegroundColor Yellow
+    Write-Host ''
+    foreach ($x in $ruins) {
+        $cor = if ($x.Codigo -eq 28) { 'Red' } else { 'Yellow' }
+        Write-Host ("  {0}" -f $x.Nome) -ForegroundColor $cor
+        Write-Host ("     {0}" -f $x.Problema) -ForegroundColor DarkGray
+        if ($x.HardwareID) { Write-Host ("     {0}" -f (Hwid-Curto $x.HardwareID)) -ForegroundColor DarkGray }
+    }
+    return $ruins
+}
+
+# ------------------------------------------------------------
+# Backup: exportar os drivers que já funcionam nesta máquina
+# ------------------------------------------------------------
+function Exportar-Drivers([string]$pasta) {
+    try { New-Item -ItemType Directory -Path $pasta -Force | Out-Null }
+    catch { Write-Host "[ERRO] Não consegui criar a pasta: $($_.Exception.Message)" -ForegroundColor Red; return $false }
+
+    Write-Host ''
+    Write-Host 'Exportando os drivers instalados. Isso leva alguns minutos...' -ForegroundColor Gray
+    Write-Host "Destino: $pasta" -ForegroundColor DarkGray
+    $t0 = Get-Date
+    & pnputil.exe /export-driver '*' "$pasta" 2>&1 | Out-Null
+
+    # O codigo de saida do pnputil nao e' confiavel aqui: quem diz a verdade
+    # e' a contagem de .inf que chegou no destino.
+    $infs = @(Get-ChildItem -LiteralPath $pasta -Recurse -Filter '*.inf' -File -ErrorAction SilentlyContinue)
+    if ($infs.Count -eq 0) {
+        Write-Host '[ERRO] Nada foi exportado. Confirme que a janela está como administrador.' -ForegroundColor Red
+        return $false
+    }
+    $bytes = 0
+    foreach ($f in @(Get-ChildItem -LiteralPath $pasta -Recurse -File -ErrorAction SilentlyContinue)) { $bytes += $f.Length }
+    Write-Host ("[OK] {0} driver(s) exportados, {1}, em {2:N0}s." -f $infs.Count, (Tam $bytes), ((Get-Date) - $t0).TotalSeconds) -ForegroundColor Green
+    return $true
+}
+
+# Garante que existe backup de hoje; pula se ja tiver
+function Garantir-Backup([string]$pastaHoje) {
+    $jaTem = @(Get-ChildItem -LiteralPath $pastaHoje -Recurse -Filter '*.inf' -File -ErrorAction SilentlyContinue)
+    if ($jaTem.Count -gt 0) {
+        Write-Host ("[OK] Backup de hoje já existe ({0} drivers). Pulando." -f $jaTem.Count) -ForegroundColor Green
+        return $true
+    }
+    Titulo 'Backup dos drivers desta máquina'
+    return (Exportar-Drivers $pastaHoje)
+}
+
+# ------------------------------------------------------------
+# Leitura de .inf - para a prévia antes de restaurar
+# ------------------------------------------------------------
+$nomesClasse = @{
+    'net'='Rede'; 'display'='Vídeo'; 'media'='Áudio'; 'system'='Chipset/Sistema'
+    'usb'='USB'; 'bluetooth'='Bluetooth'; 'hidclass'='Entrada (teclado/mouse)'
+    'mouse'='Mouse'; 'keyboard'='Teclado'; 'printer'='Impressora'; 'image'='Câmera/Scanner'
+    'scsiadapter'='Armazenamento'; 'hdc'='Armazenamento'; 'diskdrive'='Disco'
+    'monitor'='Monitor'; 'firmware'='Firmware'; 'smartcardreader'='Leitor de cartão'
+    'ports'='Portas COM/LPT'; 'battery'='Bateria'; 'biometric'='Biometria'
+    'camera'='Câmera'; 'securitydevices'='Segurança'; 'softwarecomponent'='Componente do fabricante'
+    'extension'='Extensão de driver'; 'volume'='Volume'; 'computer'='Computador'
+    'modem'='Modem'; 'sdhost'='Leitor de cartão SD'; 'usbdevice'='Dispositivo USB'
+}
+
+function Ler-Inf([string]$caminho, $mapaClasses) {
+    $info = [pscustomobject]@{
+        Arquivo  = $caminho
+        Nome     = [System.IO.Path]::GetFileName($caminho)
+        Classe   = '?'
+        Amigavel = 'Outro'
+        Provedor = '?'
+        Versao   = '-'
+        Data     = '-'
+    }
+    # Le o arquivo inteiro de proposito: o [Strings] fica no FIM do .inf, e
+    # ler so' o comeco deixaria os tokens %NOME% sem resolver.
+    $linhas = $null
+    try { $linhas = [System.IO.File]::ReadAllLines($caminho) } catch { return $info }
+
+    # Uma passada so': [Version] da' os dados, [Strings] resolve os tokens
+    $strings = @{}
+    $secao = ''
+    foreach ($l in $linhas) {
+        $t = "$l".Trim()
+        if ($t -match '^\[(.+)\]\s*$') { $secao = $matches[1].Trim().ToLowerInvariant(); continue }
+        if ($secao -eq 'strings') {
+            if ($t -match '^([^;=]+?)\s*=\s*"?([^";]*)"?\s*$') { $strings[$matches[1].Trim()] = $matches[2].Trim() }
+            continue
+        }
+        if ($secao -ne 'version') { continue }
+        if ($t -match '^(?i)class\s*=\s*([^;]+)')     { $info.Classe   = $matches[1].Trim() }
+        if ($t -match '^(?i)provider\s*=\s*([^;]+)')  { $info.Provedor = $matches[1].Trim() }
+        if ($t -match '^(?i)driverver\s*=\s*([^;]+)') {
+            $p = $matches[1].Trim() -split ','
+            if ($p.Count -ge 1) { $info.Data   = $p[0].Trim() }
+            if ($p.Count -ge 2) { $info.Versao = $p[1].Trim() }
+        }
+    }
+    if ($info.Provedor -match '^%(.+)%$') {
+        $k = $matches[1]
+        if ($strings.ContainsKey($k)) { $info.Provedor = $strings[$k] }
+    }
+    $ck = $info.Classe.ToLowerInvariant()
+    $info.Amigavel = if ($mapaClasses.ContainsKey($ck)) { $mapaClasses[$ck] } else { $info.Classe }
+    return $info
+}
+
+function Listar-Infs([string]$pasta, $mapaClasses) {
+    $arqs = @(Get-ChildItem -LiteralPath $pasta -Recurse -Filter '*.inf' -File -ErrorAction SilentlyContinue)
+    if ($arqs.Count -eq 0) { return @() }
+    Write-Host ("Lendo {0} arquivo(s) .inf..." -f $arqs.Count) -ForegroundColor Gray
+    $r = @()
+    foreach ($a in $arqs) { $r += (Ler-Inf $a.FullName $mapaClasses) }
+    return @($r | Sort-Object Amigavel, Provedor, Nome)
+}
+
+function Previa-Infs($infs) {
+    Titulo 'Prévia: o que será instalado'
+    foreach ($g in @($infs | Group-Object Amigavel | Sort-Object Count -Descending)) {
+        Write-Host ''
+        Write-Host ("  {0}  ({1})" -f $g.Name, $g.Count) -ForegroundColor Cyan
+        foreach ($i in @($g.Group | Sort-Object Provedor, Nome)) {
+            Write-Host ("     {0,-30} {1,-16} {2}" -f $i.Provedor, $i.Versao, $i.Nome) -ForegroundColor Gray
+        }
+    }
+    Write-Host ''
+    Write-Host ("TOTAL: {0} driver(s)" -f @($infs).Count) -ForegroundColor White
+    Write-Host 'Nada é apagado: o Windows mantém o driver mais adequado entre o atual e o novo.' -ForegroundColor DarkGray
+}
+
+# Codigos do pnputil, medidos: 0 = instalou, 3010 = instalou e pede reinicio,
+# 259 = o driver JA estava no repositorio (nao e' erro). Arquivo invalido
+# devolve numero NEGATIVO, entao comparar sempre com -ne, nunca com -gt.
+function Pnputil-Ok([int]$rc)  { return ($rc -eq 0 -or $rc -eq 3010 -or $rc -eq 259) }
+function Pnputil-Novo([int]$rc) { return ($rc -eq 0 -or $rc -eq 3010) }
+
+# Instala um .inf por vez: lento, mas diz exatamente qual falhou
+function Instalar-Um-A-Um($infs) {
+    $ok = 0; $ign = 0; $err = 0; $reinicia = $false
+    $tot = @($infs).Count
+    $n = 0
+    foreach ($i in $infs) {
+        $n++
+        Write-Progress -Activity 'Instalando drivers' -Status $i.Nome -PercentComplete ([int](100 * $n / $tot))
+        $saida = & pnputil.exe /add-driver "$($i.Arquivo)" /install 2>&1
+        $rc = $LASTEXITCODE
+        if ($rc -eq 3010) { $reinicia = $true }
+        if (Pnputil-Novo $rc)   { $ok++ }
+        elseif (Pnputil-Ok $rc) { $ign++ }
+        else {
+            $err++
+            Write-Host ("  [ERRO] {0}" -f $i.Nome) -ForegroundColor Red
+            $motivo = (@($saida) | Where-Object { "$_".Trim() } | Select-Object -Last 1)
+            if ($motivo) { Write-Host ("         {0}" -f "$motivo".Trim()) -ForegroundColor DarkGray }
+        }
+    }
+    Write-Progress -Activity 'Instalando drivers' -Completed
+    return [pscustomobject]@{ Ok=$ok; Ignorados=$ign; Erros=$err; Reinicia=$reinicia }
+}
+
+# Tenta a chamada unica (segundos em vez de minutos) e, se falhar,
+# refaz um por um so' para saber qual driver deu problema.
+function Instalar-Infs($infs, [string]$pastaLote) {
+    if ($pastaLote) {
+        Write-Host ("Instalando {0} driver(s) de uma vez..." -f @($infs).Count) -ForegroundColor Gray
+        $curinga = Join-Path $pastaLote '*.inf'
+        & pnputil.exe /add-driver "$curinga" /subdirs /install 2>&1 | Out-Null
+        $rc = $LASTEXITCODE
+        if (Pnputil-Ok $rc) {
+            Write-Host ''
+            if ($rc -eq 259) { Write-Host '[OK] Os drivers dessa pasta já estavam no repositório.' -ForegroundColor Green }
+            else             { Write-Host ("[OK] {0} driver(s) instalados." -f @($infs).Count) -ForegroundColor Green }
+            if ($rc -eq 3010) { Write-Host '[i] É preciso REINICIAR para concluir.' -ForegroundColor Cyan }
+            return $true
+        }
+        Write-Host ''
+        Write-Host ("[!] A instalação em lote terminou com o código {0}." -f $rc) -ForegroundColor Yellow
+        Write-Host '    Refazendo um por um para identificar o que falhou...' -ForegroundColor Yellow
+        Write-Host ''
+    }
+    $r = Instalar-Um-A-Um $infs
+    Write-Host ''
+    Write-Host ("Instalados:  {0}" -f $r.Ok) -ForegroundColor Green
+    if ($r.Ignorados -gt 0) { Write-Host ("Já existiam: {0}" -f $r.Ignorados) -ForegroundColor Yellow }
+    if ($r.Erros -gt 0) {
+        Write-Host ("Falharam:    {0}" -f $r.Erros) -ForegroundColor Red
+        Write-Host 'Driver que falha aqui costuma ser de hardware que não existe nesta máquina.' -ForegroundColor DarkGray
+    }
+    if ($r.Reinicia) { Write-Host '[i] É preciso REINICIAR para concluir.' -ForegroundColor Cyan }
+    return (($r.Ok + $r.Ignorados) -gt 0)
+}
+
+# ------------------------------------------------------------
+# Atualizações pelo Windows Update
+# ------------------------------------------------------------
+function Buscar-Atualizacoes {
+    Titulo 'Procurar atualizações de driver'
+    Write-Host 'Consultando o Windows Update. Pode levar de 1 a 3 minutos...' -ForegroundColor Gray
+
+    $ses = $null
+    try { $ses = New-Object -ComObject Microsoft.Update.Session -ErrorAction Stop }
+    catch { Write-Host "[ERRO] Não consegui falar com o Windows Update: $($_.Exception.Message)" -ForegroundColor Red; return }
+
+    $res = $null
+    # O Microsoft Update traz os drivers opcionais que o Windows Update comum esconde
+    try {
+        $b = $ses.CreateUpdateSearcher()
+        $b.ServerSelection = 3
+        $b.ServiceID = '7971f918-a847-4430-9279-4a52d1efe18d'
+        $res = $b.Search("IsInstalled=0 and Type='Driver'")
+    } catch {
+        Write-Host ("[!] Microsoft Update indisponível ({0})." -f $_.Exception.Message) -ForegroundColor Yellow
+        Write-Host '    Tentando pelo Windows Update padrão...' -ForegroundColor Yellow
+        try {
+            $b2 = $ses.CreateUpdateSearcher()
+            $res = $b2.Search("IsInstalled=0 and Type='Driver'")
+        } catch { Write-Host "[ERRO] A busca falhou: $($_.Exception.Message)" -ForegroundColor Red; return }
+    }
+
+    $achados = @($res.Updates)
+    if ($achados.Count -eq 0) {
+        Write-Host ''
+        Write-Host '[OK] Nenhuma atualização de driver pendente.' -ForegroundColor Green
+        return
+    }
+
+    Titulo ('Prévia: {0} atualização(ões) encontrada(s)' -f $achados.Count)
+    $bytes = 0
+    foreach ($u in $achados) {
+        $tam = 0
+        try { $tam = [double]$u.MaxDownloadSize } catch { $tam = 0 }
+        $bytes += $tam
+        Write-Host ("  {0}" -f $u.Title) -ForegroundColor Gray
+        $quando = ''
+        try { if ($u.DriverVerDate) { $quando = ([datetime]$u.DriverVerDate).ToString('yyyy-MM-dd') } } catch { $quando = '' }
+        if ($quando -or $tam -gt 0) {
+            Write-Host ("     {0}  {1}" -f $quando, $(if ($tam -gt 0) { Tam $tam } else { '' })) -ForegroundColor DarkGray
+        }
+    }
+    Write-Host ''
+    if ($bytes -gt 0) { Write-Host ("Download total: {0}" -f (Tam $bytes)) -ForegroundColor White }
+    Write-Host ''
+    if (-not (Confirmar 'Baixar e instalar tudo?' $true)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; return }
+
+    $col = New-Object -ComObject Microsoft.Update.UpdateColl
+    foreach ($u in $achados) {
+        try { if (-not $u.EulaAccepted) { $u.AcceptEula() } }
+        catch { Write-Host ("  [!] Não aceitei o EULA de '{0}': {1}" -f $u.Title, $_.Exception.Message) -ForegroundColor DarkGray }
+        [void]$col.Add($u)
+    }
+
+    Write-Host ''
+    Write-Host 'Baixando...' -ForegroundColor Gray
+    try { $dl = $ses.CreateUpdateDownloader(); $dl.Updates = $col; [void]$dl.Download() }
+    catch { Write-Host "[ERRO] Falha no download: $($_.Exception.Message)" -ForegroundColor Red; return }
+
+    Write-Host 'Instalando...' -ForegroundColor Gray
+    try {
+        $ins = $ses.CreateUpdateInstaller(); $ins.Updates = $col
+        $r = $ins.Install()
+        switch ([int]$r.ResultCode) {
+            2 { Write-Host '[OK] Instalado com sucesso.' -ForegroundColor Green }
+            3 { Write-Host '[!] Instalado, mas com avisos. Confira o Gerenciador de Dispositivos.' -ForegroundColor Yellow }
+            default { Write-Host ("[!] A instalação terminou com o código {0}." -f $r.ResultCode) -ForegroundColor Yellow }
+        }
+        if ($r.RebootRequired) { Write-Host '[i] É preciso REINICIAR para concluir.' -ForegroundColor Cyan }
+    } catch { Write-Host "[ERRO] Falha na instalação: $($_.Exception.Message)" -ForegroundColor Red }
+}
+
+# ------------------------------------------------------------
+# Microsoft Update Catalog
+# A unica parte do programa que depende do HTML de um site alheio.
+# Quando a Microsoft mudar a pagina, as regex abaixo param de casar.
+# ------------------------------------------------------------
+function Catalog-Preparar {
+    try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 }
+    catch { Write-Host "[!] Não consegui forçar TLS 1.2: $($_.Exception.Message)" -ForegroundColor DarkGray }
+}
+
+function Catalog-Buscar([string]$hwid) {
+    $url = 'https://www.catalog.update.microsoft.com/Search.aspx?q=' + [System.Uri]::EscapeDataString($hwid)
+    $html = $null
+    try { $html = (Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 90).Content }
+    catch {
+        Write-Host ("     [ERRO] Não consegui consultar o Catalog: {0}" -f $_.Exception.Message) -ForegroundColor Red
+        Write-Host ("     Busca manual: {0}" -f $url) -ForegroundColor White
+        return @()
+    }
+
+    # O id do link vem com aspas SIMPLES na pagina real: aceitar os dois tipos
+    $achados = @()
+    $rx = [regex]'(?s)<a[^>]+id=["'']([0-9a-fA-F\-]{36})_link["''][^>]*>(.*?)</a>(.*?)</tr>'
+    foreach ($m in $rx.Matches($html)) {
+        $celulas = @()
+        foreach ($c in [regex]::Matches($m.Groups[3].Value, '(?s)<td[^>]*>(.*?)</td>')) {
+            $txt = ($c.Groups[1].Value -replace '<[^>]+>', '').Trim()
+            if ($txt) { $celulas += $txt }
+        }
+        $data = $null; $versao = ''
+        foreach ($txt in $celulas) {
+            if (-not $data -and $txt -match '^\d{1,2}/\d{1,2}/\d{4}$') {
+                try { $data = [datetime]::Parse($txt, [System.Globalization.CultureInfo]::GetCultureInfo('en-US')) } catch { $data = $null }
+                continue
+            }
+            if (-not $versao -and $txt -match '^\d+(\.\d+){1,3}$') { $versao = $txt }
+        }
+        $achados += [pscustomobject]@{
+            Id       = $m.Groups[1].Value
+            Titulo   = ($m.Groups[2].Value -replace '<[^>]+>', '').Trim()
+            Produtos = if ($celulas.Count -gt 0) { $celulas[0] } else { '' }
+            Data     = $data
+            Versao   = $versao
+        }
+    }
+    if ($achados.Count -eq 0) { return @() }
+
+    # O titulo nao diz a versao do Windows: quem diz e' a coluna de produtos
+    $arch = if ([Environment]::Is64BitOperatingSystem) { 'x64' } else { 'x86' }
+    $win  = if ([Environment]::OSVersion.Version.Build -ge 22000) { 'Windows 11' } else { 'Windows 10' }
+    $ctx  = { param($x) "$($x.Titulo) $($x.Produtos)" }
+    $bons = @($achados | Where-Object { (& $ctx $_) -notmatch '(?i)\b(arm64|itanium)\b' })
+    if ($arch -eq 'x86') { $bons = @($bons | Where-Object { (& $ctx $_) -notmatch '(?i)x64' }) }
+    $doWin = @($bons | Where-Object { (& $ctx $_) -match [regex]::Escape($win) })
+    if ($doWin.Count -gt 0) { $bons = $doWin }
+    if ($bons.Count -eq 0) { $bons = $achados }
+
+    # A mesma atualizacao aparece repetida uma vez por produto: fica uma so'
+    $vistos = @{}
+    $unicos = @()
+    foreach ($b in @($bons | Sort-Object @{ Expression = { if ($_.Data) { $_.Data } else { [datetime]::MinValue } } } -Descending)) {
+        $chave = "$($b.Titulo)|$($b.Versao)"
+        if ($vistos.ContainsKey($chave)) { continue }
+        $vistos[$chave] = $true
+        $unicos += $b
+    }
+    return @($unicos)
+}
+
+function Catalog-Baixar([string]$updateId, [string]$destino) {
+    try { New-Item -ItemType Directory -Path $destino -Force | Out-Null }
+    catch { Write-Host ("     [ERRO] Não consegui criar a pasta: {0}" -f $_.Exception.Message) -ForegroundColor Red; return $null }
+
+    $json = '[{"size":0,"languages":"","uidInfo":"' + $updateId + '","updateID":"' + $updateId + '"}]'
+    $body = 'updateIDs=' + [System.Uri]::EscapeDataString($json) + '&updateIDsBlockedForInstall=&wsusApiPresent=&contentImport=&sku=&serverName=&ssl=&portNumber=&version='
+    $html = $null
+    try {
+        $html = (Invoke-WebRequest -Uri 'https://www.catalog.update.microsoft.com/DownloadDialog.aspx' `
+                    -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' `
+                    -UseBasicParsing -TimeoutSec 90).Content
+    } catch {
+        Write-Host ("     [ERRO] O Catalog recusou o pedido de download: {0}" -f $_.Exception.Message) -ForegroundColor Red
+        return $null
+    }
+
+    $link = @([regex]::Matches($html, '(?i)https?://[^\s]+?\.(?:cab|msu|exe)') | ForEach-Object { $_.Value } | Select-Object -Unique -First 1)
+    if ($link.Count -eq 0) {
+        Write-Host '     [ERRO] Não achei o link do arquivo na resposta do Catalog.' -ForegroundColor Red
+        Write-Host '     A página do Catalog deve ter mudado de formato.' -ForegroundColor DarkGray
+        return $null
+    }
+    $url = $link[0]
+    $arq = Join-Path $destino ([System.IO.Path]::GetFileName(($url -split '\?')[0]))
+    Write-Host ("     Baixando {0}..." -f [System.IO.Path]::GetFileName($arq)) -ForegroundColor Gray
+    try { Invoke-WebRequest -Uri $url -OutFile $arq -UseBasicParsing -TimeoutSec 600 }
+    catch { Write-Host ("     [ERRO] Falha no download: {0}" -f $_.Exception.Message) -ForegroundColor Red; return $null }
+    if (-not (Test-Path -LiteralPath $arq)) { return $null }
+    return $arq
+}
+
+# Extrai .cab/.msu com o expand.exe do proprio Windows
+function Catalog-Extrair([string]$arquivo, [string]$destino) {
+    $ext = [System.IO.Path]::GetExtension($arquivo).ToLowerInvariant()
+    if ($ext -eq '.exe') {
+        Write-Host '     [!] Veio um instalador .exe: rode-o à mão, o pnputil não abre isso.' -ForegroundColor Yellow
+        Write-Host ("     {0}" -f $arquivo) -ForegroundColor White
+        return $null
+    }
+    $dir = Join-Path $destino 'extraido'
+    try { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
+    catch { Write-Host ("     [ERRO] {0}" -f $_.Exception.Message) -ForegroundColor Red; return $null }
+
+    & expand.exe -F:* "$arquivo" "$dir" | Out-Null
+    # Um .msu traz um .cab dentro: extrai o de dentro tambem
+    foreach ($cab in @(Get-ChildItem -LiteralPath $dir -Filter '*.cab' -File -ErrorAction SilentlyContinue)) {
+        & expand.exe -F:* "$($cab.FullName)" "$dir" | Out-Null
+    }
+    $infs = @(Get-ChildItem -LiteralPath $dir -Recurse -Filter '*.inf' -File -ErrorAction SilentlyContinue)
+    if ($infs.Count -eq 0) {
+        Write-Host '     [ERRO] O pacote não tinha nenhum .inf dentro.' -ForegroundColor Red
+        return $null
+    }
+    return $dir
+}
+
+# Percorre os dispositivos sem driver e tenta resolver cada um pelo Catalog
+function Catalog-Resolver($semDriver, [string]$pastaPc, $mapaClasses) {
+    Titulo 'Microsoft Update Catalog'
+    Write-Host ("{0} dispositivo(s) sem driver. Vou procurar um a um." -f @($semDriver).Count) -ForegroundColor Gray
+    Write-Host 'Esta busca depende da página da Microsoft e pode falhar se o site mudar.' -ForegroundColor DarkGray
+    Catalog-Preparar
+
+    $resolvidos = 0
+    foreach ($d in $semDriver) {
+        $hw = Hwid-Curto $d.HardwareID
+        Write-Host ''
+        Write-Host ("  {0}" -f $d.Nome) -ForegroundColor White
+        Write-Host ("     {0}" -f $hw) -ForegroundColor DarkGray
+
+        $achados = Catalog-Buscar $hw
+        if (@($achados).Count -eq 0) {
+            Write-Host '     Nada encontrado para este dispositivo.' -ForegroundColor Yellow
+            continue
+        }
+        $melhor = $achados[0]
+        $quando = if ($melhor.Data) { $melhor.Data.ToString('yyyy-MM-dd') } else { 'sem data' }
+        Write-Host ("     Encontrado: {0}" -f $melhor.Titulo) -ForegroundColor Green
+        Write-Host ("     {0}   versão {1}" -f $quando, $(if ($melhor.Versao) { $melhor.Versao } else { '?' })) -ForegroundColor DarkGray
+        if (-not (Confirmar '     Baixar e instalar este?' $true)) { continue }
+
+        $pasta = Join-Path (Join-Path $pastaPc 'catalog') ($hw -replace '[\\&]', '_')
+        $arq = Catalog-Baixar $melhor.Id $pasta
+        if (-not $arq) { continue }
+        $dir = Catalog-Extrair $arq $pasta
+        if (-not $dir) { continue }
+
+        $infs = Listar-Infs $dir $mapaClasses
+        if (@($infs).Count -eq 0) { continue }
+        if (Instalar-Infs $infs $dir) { $resolvidos++ }
+    }
+
+    Write-Host ''
+    Write-Host ("Dispositivos resolvidos pelo Catalog: {0}" -f $resolvidos) -ForegroundColor Cyan
+}
+
+# ------------------------------------------------------------
+# Ferramenta do fabricante
+# ------------------------------------------------------------
+# Windows recem-instalado as vezes vem sem o winget. Como ele e' o que
+# instala as ferramentas dos fabricantes, o programa busca o instalador
+# oficial (o mesmo aka.ms que a Microsoft publica) e o registra sozinho.
+function Garantir-Winget {
+    $w = Get-Command winget -ErrorAction SilentlyContinue
+    if ($w) { return $w.Source }
+
+    Write-Host ''
+    Write-Host '[!] O winget não existe nesta máquina. Vou instalá-lo.' -ForegroundColor Yellow
+    try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {}
+    $pacote = Join-Path $env:TEMP 'AppInstaller.msixbundle'
+    try {
+        Write-Host '    Baixando o App Installer da Microsoft...' -ForegroundColor Gray
+        Invoke-WebRequest -Uri 'https://aka.ms/getwinget' -OutFile $pacote -UseBasicParsing -TimeoutSec 600
+    } catch {
+        Write-Host ("[ERRO] Não consegui baixar o winget: {0}" -f $_.Exception.Message) -ForegroundColor Red
+        return $null
+    }
+    try {
+        Write-Host '    Instalando...' -ForegroundColor Gray
+        Add-AppxPackage -Path $pacote -ErrorAction Stop
+    } catch {
+        Write-Host ("[ERRO] Não consegui instalar o winget: {0}" -f $_.Exception.Message) -ForegroundColor Red
+        Write-Host '    Instale a "Instalador de Aplicativo" pela Microsoft Store e tente de novo.' -ForegroundColor White
+        return $null
+    }
+    # O winget entra no PATH do usuario: recarrega sem precisar reabrir
+    $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
+    $w = Get-Command winget -ErrorAction SilentlyContinue
+    if ($w) { Write-Host '[OK] winget instalado.' -ForegroundColor Green; return $w.Source }
+    Write-Host '[!] O winget foi instalado mas só aparece depois de reabrir o programa.' -ForegroundColor Yellow
+    return $null
+}
+
+function Tentar-Winget([string]$id, [string]$nome, [string]$url, [string]$fonte) {
+    if (-not (Garantir-Winget)) { if ($url) { Abrir-Url $url }; return $false }
+    Write-Host ("Instalando {0} pelo winget..." -f $nome) -ForegroundColor Gray
+    if ($fonte) { & winget install --id $id -e --source $fonte --accept-package-agreements --accept-source-agreements }
+    else        { & winget install --id $id -e --accept-package-agreements --accept-source-agreements }
+    $rc = $LASTEXITCODE
+    # -1978335189 = "nenhuma atualizacao disponivel", ou seja, ja esta instalado
+    if ($rc -eq 0 -or $rc -eq -1978335189) {
+        Write-Host ("[OK] {0} pronto." -f $nome) -ForegroundColor Green
+        return $true
+    }
+    Write-Host ("[!] O winget não instalou o {0} (código {1})." -f $nome, $rc) -ForegroundColor Yellow
+    if ($url) { Abrir-Url $url }
+    return $false
+}
+
+function Achar-Exe([string]$nome, $pastas) {
+    $c = Get-Command $nome -ErrorAction SilentlyContinue
+    if ($c) { return $c.Source }
+    foreach ($b in $pastas) {
+        if (-not $b -or -not (Test-Path -LiteralPath $b)) { continue }
+        $a = Get-ChildItem -LiteralPath $b -Recurse -Filter $nome -File -ErrorAction SilentlyContinue |
+             Sort-Object LastWriteTime -Descending | Select-Object -First 1
+        if ($a) { return $a.FullName }
+    }
+    return $null
+}
+
+function Achar-Dcu {
+    foreach ($b in @("$env:ProgramFiles\Dell\CommandUpdate", "${env:ProgramFiles(x86)}\Dell\CommandUpdate")) {
+        $p = Join-Path $b 'dcu-cli.exe'
+        if (Test-Path -LiteralPath $p) { return $p }
+    }
+    return $null
+}
+
+function Ferramenta-Fabricante($maq, [string]$pastaPc) {
+    Titulo 'Ferramenta do fabricante'
+    Write-Host ("Fabricante: {0}" -f $maq.Fabricante) -ForegroundColor White
+    Write-Host ("Modelo:     {0}" -f $maq.Modelo) -ForegroundColor White
+    Write-Host ("Placa-mãe:  {0}" -f $maq.PlacaMae) -ForegroundColor White
+    Write-Host ''
+
+    switch ($maq.Marca) {
+        'dell' {
+            $dcu = Achar-Dcu
+            if (-not $dcu) {
+                Write-Host 'O Dell Command | Update entrega o driver homologado para este modelo exato.' -ForegroundColor Gray
+                if (Confirmar 'Instalar o Dell Command | Update agora?' $true) {
+                    [void](Tentar-Winget 'Dell.CommandUpdate' 'Dell Command | Update' 'https://www.dell.com/support/kbdoc/pt-br/000177325/dell-command-update' '')
+                    $dcu = Achar-Dcu
+                }
+            }
+            if (-not $dcu) {
+                Write-Host '[!] Não achei o dcu-cli.exe. Abra o Dell Command Update pelo menu Iniciar.' -ForegroundColor Yellow
+            } else {
+                Write-Host ''
+                Write-Host 'Procurando atualizações na Dell...' -ForegroundColor Gray
+                & $dcu /scan
+                Write-Host ''
+                if (Confirmar 'Aplicar as atualizações encontradas?' $true) {
+                    & $dcu /applyUpdates -reboot=disable
+                    Write-Host '[i] Pode ser preciso reiniciar.' -ForegroundColor Cyan
+                }
+            }
+        }
+        'hp' {
+            $hpia = Achar-Exe 'HPImageAssistant.exe' @("$env:ProgramFiles\HP", "${env:ProgramFiles(x86)}\HP", 'C:\SWSetup')
+            if (-not $hpia) {
+                Write-Host 'O HP Image Assistant entrega o driver homologado para este modelo exato.' -ForegroundColor Gray
+                if (Confirmar 'Instalar o HP Image Assistant agora?' $true) {
+                    [void](Tentar-Winget 'HP.ImageAssistant' 'HP Image Assistant' 'https://hp.com/go/clientmanagement' '')
+                    $hpia = Achar-Exe 'HPImageAssistant.exe' @("$env:ProgramFiles\HP", "${env:ProgramFiles(x86)}\HP", 'C:\SWSetup')
+                }
+            }
+            if (-not $hpia) {
+                Write-Host '[!] Não achei o HPImageAssistant.exe. Abra o HP Image Assistant pelo menu Iniciar.' -ForegroundColor Yellow
+            } else {
+                $rel = Join-Path $pastaPc 'hpia'
+                try { New-Item -ItemType Directory -Path $rel -Force | Out-Null } catch { Write-Host "[!] $($_.Exception.Message)" -ForegroundColor DarkGray }
+                Write-Host ''
+                Write-Host 'Analisando e instalando os drivers da HP. Isso demora...' -ForegroundColor Gray
+                & $hpia /Operation:Analyze /Action:Install /Selection:All /Category:Drivers /Silent `
+                        /ReportFolder:"$rel" /SoftpaqDownloadFolder:"$rel"
+                Write-Host ("[OK] HPIA terminou (código {0}). Relatório em {1}" -f $LASTEXITCODE, $rel) -ForegroundColor Green
+                Write-Host '[i] Pode ser preciso reiniciar.' -ForegroundColor Cyan
+            }
+        }
+        'lenovo' {
+            $lsu = Achar-Exe 'Tvsu.exe' @("$env:ProgramFiles\Lenovo\System Update", "${env:ProgramFiles(x86)}\Lenovo\System Update")
+            if (-not $lsu) {
+                Write-Host 'Na Lenovo, quem faz esse trabalho é o System Update.' -ForegroundColor Gray
+                if (Confirmar 'Instalar o Lenovo System Update agora?' $true) {
+                    [void](Tentar-Winget 'Lenovo.SystemUpdate' 'Lenovo System Update' 'https://support.lenovo.com/solutions/ht003029' '')
+                    $lsu = Achar-Exe 'Tvsu.exe' @("$env:ProgramFiles\Lenovo\System Update", "${env:ProgramFiles(x86)}\Lenovo\System Update")
+                }
+            }
+            if ($lsu) {
+                Write-Host ''
+                Write-Host 'Abrindo o Lenovo System Update...' -ForegroundColor Gray
+                try { Start-Process -FilePath $lsu | Out-Null }
+                catch { Write-Host "[!] Não consegui abrir: $($_.Exception.Message)" -ForegroundColor Yellow }
+            } else {
+                Abrir-Url 'https://support.lenovo.com/solutions/ht003029'
+            }
+        }
+        default {
+            Write-Host 'Esta máquina não tem ferramenta de linha de comando do fabricante.' -ForegroundColor Yellow
+            Write-Host 'Em PC montado o que importa é a placa-mãe: chipset, rede e áudio vêm dela.' -ForegroundColor Gray
+            Write-Host ''
+            Abrir-Url ('https://www.bing.com/search?q=' + [System.Uri]::EscapeDataString("$($maq.PlacaMae) drivers"))
+        }
+    }
+
+    # Placa de video e' caso a parte: o pacote oficial traz o painel de controle
+    $todas = @(Placas-Video)
+    $gpus  = @($todas | Where-Object { $_.Tipo -eq 'nvidia' -or $_.Tipo -eq 'amd' })
+    if ($gpus.Count -gt 0) {
+        Titulo 'Placa de vídeo dedicada'
+        foreach ($g in $gpus) { Write-Host ("  {0}   (driver atual {1})" -f $g.Nome, $g.Versao) -ForegroundColor White }
+        Write-Host ''
+        Write-Host 'O driver restaurado por INF faz a placa funcionar, mas não traz o painel de controle.' -ForegroundColor Gray
+
+        if (@($gpus | Where-Object { $_.Tipo -eq 'nvidia' }).Count -gt 0) {
+            Write-Host ''
+            Write-Host 'A NVIDIA não publica o driver no winget. Quem instala e mantém é o NVIDIA App.' -ForegroundColor Gray
+            if (Confirmar 'Instalar o NVIDIA App (ele baixa o driver)?' $true) {
+                if (-not (Tentar-Winget 'XP8CLZL93F5Z4P' 'NVIDIA App' 'https://www.nvidia.com/pt-br/drivers/' 'msstore')) {
+                    Write-Host '    Baixe o driver direto na página que abri.' -ForegroundColor White
+                }
+            }
+        }
+        if (@($gpus | Where-Object { $_.Tipo -eq 'amd' }).Count -gt 0) {
+            Write-Host ''
+            Write-Host 'A AMD não publica o Adrenalin no winget: só na página oficial.' -ForegroundColor Gray
+            if (Confirmar 'Abrir a página do driver AMD?' $true) { Abrir-Url 'https://www.amd.com/pt/support' }
+        }
+    }
+
+    # Intel tem CLI de verdade e cobre chipset, Wi-Fi e video integrado
+    if (@($todas | Where-Object { $_.Tipo -eq 'intel' }).Count -gt 0 -or $maq.PlacaMae -match '(?i)intel') {
+        Titulo 'Componentes Intel'
+        Write-Host 'O Intel Driver & Support Assistant cuida de chipset, Wi-Fi e vídeo integrado.' -ForegroundColor Gray
+        if (Confirmar 'Instalar o Intel Driver & Support Assistant?' $false) {
+            [void](Tentar-Winget 'Intel.IntelDriverAndSupportAssistant' 'Intel DSA' 'https://www.intel.com.br/content/www/br/pt/support/intel-driver-support-assistant.html' '')
+        }
+    }
+}
+
+# ------------------------------------------------------------
+# Início
+# ------------------------------------------------------------
+Abrir 'Drivers'
+$pnp = Get-Command pnputil.exe -ErrorAction SilentlyContinue
+if (-not $pnp) {
+    Write-Host ''
+    Write-Host '[ERRO] O pnputil.exe não foi encontrado. Ele faz parte do Windows.' -ForegroundColor Red
+    Pausar; exit 1
+}
+Write-Host ("[OK] pnputil: {0}" -f $pnp.Source) -ForegroundColor Green
+
+$maquina = Detectar-Maquina
+Write-Host ("[OK] Máquina: {0} {1}" -f $maquina.Fabricante, $maquina.Modelo) -ForegroundColor Green
+if ($maquina.Marca -eq 'montado' -or $maquina.Marca -eq 'outro') {
+    Write-Host ("[OK] Placa-mãe: {0}" -f $maquina.PlacaMae) -ForegroundColor Green
+}
+
+$config = Carregar-Config
+$padrao = if ($config -and $config.UltimaPasta) { $config.UltimaPasta } else { Join-Path $env:USERPROFILE 'Downloads\Drivers' }
+$raiz   = Pedir-Saida $padrao
+Salvar-Config ([pscustomobject]@{ UltimaPasta = $raiz })
+
+$pastaPc   = Join-Path $raiz $env:COMPUTERNAME
+$pastaHoje = Join-Path $pastaPc (Get-Date -Format 'yyyy-MM-dd')
+try { New-Item -ItemType Directory -Path $pastaPc -Force | Out-Null }
+catch { Write-Host "[ERRO] Não consegui criar a pasta desta máquina: $($_.Exception.Message)" -ForegroundColor Red; Pausar; exit 1 }
+
+# O backup completo costuma ocupar de 0,5 a 3 GB
+try {
+    $un = (Get-Item -LiteralPath $raiz).PSDrive
+    if ($un -and $un.Free -and $un.Free -lt 5GB) {
+        Write-Host ("[!] Só restam {0} nessa unidade. O backup costuma ocupar de 0,5 a 3 GB." -f (Tam $un.Free)) -ForegroundColor Yellow
+    }
+} catch { Write-Host "[!] Não consegui checar o espaço livre: $($_.Exception.Message)" -ForegroundColor DarkGray }
+
+[void](Garantir-Backup $pastaHoje)
+
+# Sugere sempre o backup mais recente daquela maquina
+function Backup-Mais-Recente([string]$pastaPc, [string]$pastaHoje) {
+    $ult = @(Get-ChildItem -LiteralPath $pastaPc -Directory -ErrorAction SilentlyContinue |
+             Where-Object { $_.Name -match '^\d{4}-\d{2}-\d{2}' } | Sort-Object Name -Descending | Select-Object -First 1)
+    if ($ult.Count -gt 0) { return $ult[0].FullName }
+    return $pastaHoje
+}
+
+# ------------------------------------------------------------
+# Menu
+# ------------------------------------------------------------
+$rotuloFab = switch ($maquina.Marca) {
+    'dell'   { 'Dell Command | Update (driver oficial do modelo)' }
+    'hp'     { 'HP Image Assistant (driver oficial do modelo)' }
+    'lenovo' { 'Lenovo System Update (driver oficial do modelo)' }
+    default  { 'Placa-mãe e placa de vídeo (páginas oficiais)' }
+}
+
+while ($true) {
+    Titulo 'DRIVERS'
+    Write-Host ("  {0}  |  pasta: {1}" -f $env:COMPUTERNAME, $raiz) -ForegroundColor DarkGray
+    Write-Host ''
+    Write-Host '> [1] Inventário: o que tem e o que falta' -ForegroundColor White
+    Write-Host '  [2] Fazer um novo backup agora' -ForegroundColor Gray
+    Write-Host '  [3] Restaurar drivers de uma pasta' -ForegroundColor Gray
+    Write-Host '  [4] Restaurar só o driver de rede (destrava a internet)' -ForegroundColor Gray
+    Write-Host '  [5] Procurar atualizações no Windows Update' -ForegroundColor Gray
+    Write-Host ("  [6] {0}" -f $rotuloFab) -ForegroundColor Gray
+    Write-Host ''
+    Write-Host '  [0] Sair' -ForegroundColor DarkGray
+    Write-Host 'Enter = usar a opção padrão [1]' -ForegroundColor DarkGray
+    $op = Read-Host 'Opção (0-6)'
+    if ([string]::IsNullOrWhiteSpace($op)) { $op = '1' }
+    $op = $op.Trim()
+    if ($op -eq '0') { break }
+
+    switch ($op) {
+
+        '1' {
+            Titulo 'Inventário'
+            Write-Host 'Lendo os dispositivos...' -ForegroundColor Gray
+            $disp  = Coletar-Dispositivos $codigosProblema
+            $ruins = Mostrar-Inventario $disp
+            Salvar-Inventario $disp (Join-Path $pastaPc 'inventario.csv')
+            $sem = @($ruins | Where-Object { $_.Codigo -eq 28 -and $_.HardwareID })
+            if ($sem.Count -gt 0) {
+                Write-Host ''
+                Write-Host 'O Windows Update (opção 5) resolve a maior parte disso.' -ForegroundColor Gray
+                if (Confirmar 'Procurar esses no Microsoft Update Catalog agora?' $true) {
+                    Catalog-Resolver $sem $pastaPc $nomesClasse
+                }
+            }
+            Pausar
+        }
+
+        '2' {
+            Titulo 'Novo backup'
+            [void](Exportar-Drivers (Join-Path $pastaPc (Get-Date -Format 'yyyy-MM-dd_HHmm')))
+            Pausar
+        }
+
+        '3' {
+            Titulo 'Restaurar drivers'
+            $de = Pedir-Pasta (Backup-Mais-Recente $pastaPc $pastaHoje) 'Enter = usar esta pasta   |   ou cole a pasta de outro PC / do pendrive'
+            if (-not $de) { Pausar; continue }
+            $infs = Listar-Infs $de $nomesClasse
+            if (@($infs).Count -eq 0) { Write-Host '[!] Nenhum .inf nessa pasta.' -ForegroundColor Yellow; Pausar; continue }
+            Previa-Infs $infs
+            Write-Host ''
+            if (-not (Confirmar 'Instalar esses drivers?' $true)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            Titulo 'Instalando'
+            [void](Instalar-Infs $infs $de)
+            Write-Host ''
+            if (Confirmar 'Procurar atualizações no Windows Update agora?' $true) { Buscar-Atualizacoes }
+            Pausar
+        }
+
+        '4' {
+            Titulo 'Restaurar só o driver de rede'
+            $de = Pedir-Pasta (Backup-Mais-Recente $pastaPc $pastaHoje) 'Enter = usar esta pasta   |   ou cole a pasta do pendrive'
+            if (-not $de) { Pausar; continue }
+            $rede = @((Listar-Infs $de $nomesClasse) | Where-Object { $_.Classe -match '^(?i)net$' })
+            if ($rede.Count -eq 0) { Write-Host '[!] Nenhum driver de rede nessa pasta.' -ForegroundColor Yellow; Pausar; continue }
+            Previa-Infs $rede
+            Write-Host ''
+            if (-not (Confirmar 'Instalar só esses?' $true)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            Titulo 'Instalando'
+            # Sem lote: a pasta tem drivers de tudo, e aqui so' a rede pode entrar
+            [void](Instalar-Infs $rede $null)
+            Pausar
+        }
+
+        '5' {
+            Buscar-Atualizacoes
+            $sem = @((Coletar-Dispositivos $codigosProblema) | Where-Object { $_.Codigo -eq 28 -and $_.HardwareID })
+            if ($sem.Count -gt 0) {
+                Write-Host ''
+                Write-Host ("[!] Ainda faltam {0} dispositivo(s) sem driver." -f $sem.Count) -ForegroundColor Yellow
+                if (Confirmar 'Procurar esses no Microsoft Update Catalog?' $true) {
+                    Catalog-Resolver $sem $pastaPc $nomesClasse
+                }
+            }
+            Pausar
+        }
+
+        '6' { Ferramenta-Fabricante $maquina $pastaPc; Pausar }
+
+        default { Write-Host '[!] Escolha de 0 a 6.' -ForegroundColor Yellow; Start-Sleep -Seconds 1 }
+    }
+}
+
+Titulo 'Concluído'
+Write-Host ("Pasta desta máquina: {0}" -f $pastaPc) -ForegroundColor White
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
@@ -0,0 +1,935 @@+<# :
+@echo off
+set "SELF=%~f0"
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText($env:SELF,[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Debloat - tira o excesso do Windows, com prévia e desfazer
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Debloat' } catch {}
+
+# ------------------------------------------------------------
+# Auto-elevação: mexer em serviço, tarefa e HKLM exige administrador
+# ------------------------------------------------------------
+$souAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
+    ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $souAdmin) {
+    Write-Host 'Solicitando privilégios de administrador...' -ForegroundColor Yellow
+    try {
+        Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', "`"$env:SELF`"" -Verb RunAs | Out-Null
+    } catch {
+        Write-Host "[ERRO] Sem administrador não dá para mexer no sistema: $($_.Exception.Message)" -ForegroundColor Red
+        Start-Sleep -Seconds 3
+    }
+    exit
+}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para voltar ao menu...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+function Tam($bytes) {
+    if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) }
+    elseif ($bytes -ge 1MB) { '{0:N1} MB' -f ($bytes/1MB) }
+    else { '{0:N0} KB' -f ($bytes/1KB) }
+}
+
+$configDir  = Join-Path $env:APPDATA 'Debloat'
+$configFile = Join-Path $configDir 'config.json'
+function Carregar-Config { if (Test-Path -LiteralPath $configFile) { try { return Get-Content -LiteralPath $configFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch {} }; return $null }
+function Salvar-Config($o) { try { New-Item -ItemType Directory -Path $configDir -Force | Out-Null; $o | ConvertTo-Json | Set-Content -LiteralPath $configFile -Encoding UTF8 } catch {} }
+
+# Pergunta a pasta de saida: sugere a ultima usada e cria se nao existir
+function Pedir-Saida($padrao) {
+    while ($true) {
+        Titulo 'Pasta dos relatórios'
+        Write-Host "  $padrao" -ForegroundColor White
+        Write-Host 'Enter = usar esta pasta   |   ou cole/digite outra' -ForegroundColor DarkGray
+        $r = Read-Host 'Pasta'
+        $d = if ([string]::IsNullOrWhiteSpace($r)) { $padrao } else { $r.Trim().Trim('"') }
+        try { New-Item -ItemType Directory -Path $d -Force | Out-Null; return (Resolve-Path -LiteralPath $d).Path }
+        catch { Write-Host '[!] Não foi possível criar/usar essa pasta.' -ForegroundColor Yellow }
+    }
+}
+
+function Abrir($titulo) {
+    Titulo $titulo
+    Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+    Write-Host ''
+    Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
+}
+
+# Pergunta S/n; Enter aceita o padrao
+function Confirmar([string]$pergunta, [bool]$padraoSim) {
+    $dica = if ($padraoSim) { '(S/n)' } else { '(s/N)' }
+    $r = Read-Host "$pergunta $dica"
+    if ([string]::IsNullOrWhiteSpace($r)) { return $padraoSim }
+    return ($r.Trim() -match '^(?i)(s|sim|y|yes)$')
+}
+
+function Abrir-Url([string]$u) {
+    try { Start-Process $u | Out-Null; Write-Host "  Abrindo: $u" -ForegroundColor Gray }
+    catch { Write-Host "  Abra no navegador: $u" -ForegroundColor White }
+}
+
+# ------------------------------------------------------------
+# Perfil da máquina: o que existe aqui muda o que é recomendado
+# ------------------------------------------------------------
+function Detectar-Perfil {
+    $ram = 0
+    try { $ram = [math]::Round((Get-CimInstance Win32_ComputerSystem -ErrorAction Stop).TotalPhysicalMemory / 1GB) }
+    catch { Write-Host "[!] Não li a memória: $($_.Exception.Message)" -ForegroundColor DarkGray }
+
+    $gpuDedicada = $false
+    try {
+        foreach ($v in @(Get-CimInstance Win32_VideoController -ErrorAction Stop)) {
+            if ("$($v.Name)" -match '(?i)nvidia|geforce|radeon|rtx|gtx|quadro|arc a\d') { $gpuDedicada = $true }
+        }
+    } catch { Write-Host "[!] Não li a placa de vídeo: $($_.Exception.Message)" -ForegroundColor DarkGray }
+
+    # Lojas de jogos instaladas
+    $pastasJogo = @(
+        "${env:ProgramFiles(x86)}\Steam\steam.exe"
+        "$env:ProgramFiles\Epic Games"
+        "${env:ProgramFiles(x86)}\Epic Games"
+        "$env:ProgramFiles\GOG Galaxy"
+        "${env:ProgramFiles(x86)}\Battle.net"
+        "$env:ProgramFiles\Battle.net"
+        "$env:LOCALAPPDATA\Programs\Ubisoft Game Launcher"
+    )
+    $jogos = $false
+    foreach ($p in $pastasJogo) { if ($p -and (Test-Path -LiteralPath $p)) { $jogos = $true; break } }
+
+    # Everything (voidtools): quem tem isso nao depende da busca do Windows
+    $everything = $false
+    foreach ($p in @("$env:ProgramFiles\Everything\Everything.exe", "${env:ProgramFiles(x86)}\Everything\Everything.exe")) {
+        if (Test-Path -LiteralPath $p) { $everything = $true; break } }
+    if (-not $everything -and (Get-Process -Name 'Everything' -ErrorAction SilentlyContinue)) { $everything = $true }
+
+    $office = $false
+    foreach ($p in @("$env:ProgramFiles\Microsoft Office\root\Office16", "${env:ProgramFiles(x86)}\Microsoft Office\root\Office16")) {
+        if (Test-Path -LiteralPath $p) { $office = $true; break } }
+
+    return [pscustomobject]@{
+        RamGB       = $ram
+        GpuDedicada = $gpuDedicada
+        Jogos       = $jogos
+        Everything  = $everything
+        Office      = $office
+    }
+}
+
+function Mostrar-Perfil($perfil) {
+    Write-Host ("[OK] {0} GB de RAM" -f $perfil.RamGB) -ForegroundColor Green
+    if ($perfil.Jogos -or $perfil.GpuDedicada) {
+        Write-Host '[i] Máquina de jogos: Xbox e Game Bar ficam FORA do automático.' -ForegroundColor Cyan
+    }
+    if ($perfil.Everything) {
+        Write-Host '[i] Everything instalado: dá para aliviar a busca do Windows.' -ForegroundColor Cyan
+    }
+    if ($perfil.Office) {
+        Write-Host '[i] Office instalado: Teams e OneNote ficam fora do automático.' -ForegroundColor Cyan
+    }
+    if ($perfil.RamGB -ge 32) {
+        Write-Host '[i] Com essa quantidade de RAM, mexer em serviço quase não muda nada.' -ForegroundColor DarkGray
+    }
+}
+
+# ------------------------------------------------------------
+# Diário do desfazer
+# Toda alteracao e' gravada ANTES de acontecer, com o valor anterior.
+# Fica no %APPDATA% para o desfazer funcionar sem o pendrive por perto.
+# ------------------------------------------------------------
+function Carregar-Diario([string]$arquivo) {
+    if (Test-Path -LiteralPath $arquivo) {
+        try { return Get-Content -LiteralPath $arquivo -Raw -Encoding UTF8 | ConvertFrom-Json }
+        catch { Write-Host "[!] O diário está ilegível ($($_.Exception.Message)). Vou começar um novo." -ForegroundColor Yellow }
+    }
+    return [pscustomobject]@{ versao = 1; maquina = $env:COMPUTERNAME; execucoes = @() }
+}
+
+function Gravar-Diario([string]$arquivo, $diario, $acoes, [string]$copiaEm) {
+    if (@($acoes).Count -eq 0) { return }
+    $exec = [pscustomobject]@{ data = (Get-Date).ToString('s'); acoes = @($acoes) }
+    $diario.execucoes = @($diario.execucoes) + $exec
+    try {
+        New-Item -ItemType Directory -Path (Split-Path $arquivo -Parent) -Force | Out-Null
+        # Depth: sem isso o ConvertTo-Json corta em 2 niveis e o diario sai vazio
+        $diario | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $arquivo -Encoding UTF8
+        if ($copiaEm) {
+            try { Copy-Item -LiteralPath $arquivo -Destination (Join-Path $copiaEm 'desfazer.json') -Force }
+            catch { Write-Host "[!] Não copiei o diário para a pasta: $($_.Exception.Message)" -ForegroundColor DarkGray }
+        }
+    } catch {
+        Write-Host "[ERRO] Não consegui gravar o diário: $($_.Exception.Message)" -ForegroundColor Red
+        Write-Host '       As alterações foram feitas, mas o desfazer não vai enxergá-las.' -ForegroundColor Yellow
+    }
+}
+
+# ------------------------------------------------------------
+# Ponto de restauração
+# ------------------------------------------------------------
+function Criar-Ponto-Restauracao {
+    Write-Host 'Criando ponto de restauração...' -ForegroundColor Gray
+    try {
+        Checkpoint-Computer -Description 'Antes do Debloat (fast-scripts)' -RestorePointType 'MODIFY_SETTINGS' -ErrorAction Stop
+        Write-Host '[OK] Ponto de restauração criado.' -ForegroundColor Green
+        return $true
+    } catch {
+        $m = $_.Exception.Message
+        Write-Host "[!] Não criei o ponto de restauração: $m" -ForegroundColor Yellow
+        if ($m -match '(?i)frequ|1440|already') {
+            Write-Host '    O Windows só permite um ponto a cada 24 h. Já deve existir um de hoje.' -ForegroundColor DarkGray
+        } else {
+            Write-Host '    A Proteção do Sistema pode estar desligada nesta máquina.' -ForegroundColor DarkGray
+            if (Confirmar '    Ligar a Proteção do Sistema no C: agora?' $false) {
+                try { Enable-ComputerRestore -Drive 'C:\' -ErrorAction Stop; Write-Host '    [OK] Ligada.' -ForegroundColor Green }
+                catch { Write-Host "    [!] Não consegui ligar: $($_.Exception.Message)" -ForegroundColor Yellow }
+            }
+        }
+        Write-Host '    O desfazer deste programa continua funcionando: ele usa o diário próprio.' -ForegroundColor DarkGray
+        return $false
+    }
+}
+
+# ------------------------------------------------------------
+# Motor de registro
+# ------------------------------------------------------------
+function Ler-Valor([string]$caminho, [string]$nome) {
+    try {
+        $i = Get-ItemProperty -LiteralPath $caminho -Name $nome -ErrorAction Stop
+        return [pscustomobject]@{ Existe = $true; Valor = $i.$nome }
+    } catch { return [pscustomobject]@{ Existe = $false; Valor = $null } }
+}
+
+# Grava e devolve a acao para o diario (ou $null se ja estava certo)
+function Escrever-Valor([string]$caminho, [string]$nome, $valor, [string]$tipo) {
+    $antes = Ler-Valor $caminho $nome
+    if ($antes.Existe -and "$($antes.Valor)" -eq "$valor") { return $null }
+    try {
+        if (-not (Test-Path -LiteralPath $caminho)) { New-Item -Path $caminho -Force | Out-Null }
+        New-ItemProperty -LiteralPath $caminho -Name $nome -Value $valor -PropertyType $tipo -Force -ErrorAction Stop | Out-Null
+    } catch {
+        Write-Host ("  [ERRO] {0} \ {1}: {2}" -f $caminho, $nome, $_.Exception.Message) -ForegroundColor Red
+        return $null
+    }
+    return [pscustomobject]@{
+        tipo         = 'registro'
+        caminho      = $caminho
+        nome         = $nome
+        existiaAntes = $antes.Existe
+        valorAntes   = $antes.Valor
+        tipoValor    = $tipo
+        valorDepois  = $valor
+    }
+}
+
+function Reverter-Valor($a) {
+    try {
+        if (-not (Test-Path -LiteralPath $a.caminho)) { return $true }
+        if ($a.existiaAntes) {
+            New-ItemProperty -LiteralPath $a.caminho -Name $a.nome -Value $a.valorAntes -PropertyType $a.tipoValor -Force -ErrorAction Stop | Out-Null
+        } else {
+            Remove-ItemProperty -LiteralPath $a.caminho -Name $a.nome -Force -ErrorAction SilentlyContinue
+        }
+        return $true
+    } catch {
+        Write-Host ("  [ERRO] {0} \ {1}: {2}" -f $a.caminho, $a.nome, $_.Exception.Message) -ForegroundColor Red
+        return $false
+    }
+}
+
+# ------------------------------------------------------------
+# Tabelas do que o programa sabe mexer
+# Chaves conferidas na documentacao da Microsoft e em projetos
+# abertos de debloat; nao foram escritas de memoria.
+# ------------------------------------------------------------
+function Tabela-Tweaks {
+    $HKCU = 'HKCU:\SOFTWARE'; $HKLM = 'HKLM:\SOFTWARE'
+    @(
+        # --- privacidade e telemetria ---
+        [pscustomobject]@{ Area='privacidade'; Nome='Telemetria no mínimo';                  Caminho="$HKLM\Microsoft\Windows\CurrentVersion\Policies\DataCollection"; Chave='AllowTelemetry'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='ID de anúncios desligado';              Caminho="$HKCU\Microsoft\Windows\CurrentVersion\AdvertisingInfo"; Chave='Enabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Experiências personalizadas';           Caminho="$HKCU\Microsoft\Windows\CurrentVersion\Privacy"; Chave='TailoredExperiencesWithDiagnosticDataEnabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Histórico de atividades';               Caminho="$HKLM\Policies\Microsoft\Windows\System"; Chave='PublishUserActivities'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Rastreio de apps abertos';              Caminho="$HKCU\Microsoft\Windows\CurrentVersion\Explorer\Advanced"; Chave='Start_TrackProgs'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Reconhecimento de voz online';          Caminho="$HKCU\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy"; Chave='HasAccepted'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Coleta de escrita à mão';               Caminho="$HKCU\Microsoft\InputPersonalization"; Chave='RestrictImplicitInkCollection'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Coleta de texto digitado';              Caminho="$HKCU\Microsoft\InputPersonalization"; Chave='RestrictImplicitTextCollection'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Personalização de digitação';           Caminho="$HKCU\Microsoft\Input\TIPC"; Chave='Enabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='privacidade'; Nome='Pedidos de feedback';                   Caminho="$HKCU\Microsoft\Siuf\Rules"; Chave='NumberOfSIUFInPeriod'; Valor=0; Tipo='DWord' }
+
+        # --- Copilot, Recall e IA ---
+        [pscustomobject]@{ Area='ia'; Nome='Copilot desligado (máquina)';                    Caminho="$HKLM\Policies\Microsoft\Windows\WindowsCopilot"; Chave='TurnOffWindowsCopilot'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Copilot desligado (usuário)';                    Caminho="$HKCU\Policies\Microsoft\Windows\WindowsCopilot"; Chave='TurnOffWindowsCopilot'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Botão do Copilot fora da barra';                 Caminho="$HKCU\Microsoft\Windows\CurrentVersion\Explorer\Advanced"; Chave='ShowCopilotButton'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Recall: análise de dados desligada';             Caminho="$HKLM\Policies\Microsoft\Windows\WindowsAI"; Chave='DisableAIDataAnalysis'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Recall: não deixar habilitar';                   Caminho="$HKLM\Policies\Microsoft\Windows\WindowsAI"; Chave='AllowRecallEnablement'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Recall: não salvar capturas';                    Caminho="$HKLM\Policies\Microsoft\Windows\WindowsAI"; Chave='TurnOffSavingSnapshots'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Recall: usuário atual';                          Caminho="$HKCU\Policies\Microsoft\Windows\WindowsAI"; Chave='DisableAIDataAnalysis'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='Click to Do desligado';                          Caminho="$HKLM\Policies\Microsoft\Windows\WindowsAI"; Chave='DisableClickToDo'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='ia'; Nome='IA no Edge desligada';                           Caminho="$HKLM\Policies\Microsoft\Edge"; Chave='HubsSidebarEnabled'; Valor=0; Tipo='DWord' }
+
+        # --- anuncios, sugestoes e irritacoes ---
+        [pscustomobject]@{ Area='irritacao'; Nome='Boas-vindas depois de atualizar';         Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SubscribedContent-310093Enabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Sugestões no Iniciar';                    Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SubscribedContent-338388Enabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Apps sugeridos no Iniciar';               Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SystemPaneSuggestionsEnabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Dicas e truques do Windows';              Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SubscribedContent-338389Enabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Dicas na tela (soft landing)';            Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SoftLandingEnabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Conteúdo sugerido em Configurações';      Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SubscribedContent-338393Enabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Instalação silenciosa de apps';           Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='SilentInstalledAppsEnabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Recomendados no Iniciar escondidos';      Caminho="$HKCU\Policies\Microsoft\Windows\Explorer"; Chave='HideRecommendedSection'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Anúncios do Microsoft 365';               Caminho="$HKLM\Policies\Microsoft\Windows\CloudContent"; Chave='DisableConsumerAccountStateContent'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Bing fora da busca do Iniciar';           Caminho="$HKCU\Policies\Microsoft\Windows\Explorer"; Chave='DisableSearchBoxSuggestions'; Valor=1; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Cortana fora da busca';                   Caminho="$HKLM\Policies\Microsoft\Windows\Windows Search"; Chave='AllowCortana'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Destaques da busca desligados';           Caminho="$HKCU\Microsoft\Windows\CurrentVersion\SearchSettings"; Chave='IsDynamicSearchBoxEnabled'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Widgets fora da barra';                   Caminho="$HKCU\Microsoft\Windows\CurrentVersion\Explorer\Advanced"; Chave='TaskbarDa'; Valor=0; Tipo='DWord' }
+        [pscustomobject]@{ Area='irritacao'; Nome='Dicas na tela de bloqueio';               Caminho="$HKCU\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"; Chave='RotatingLockScreenOverlayEnabled'; Valor=0; Tipo='DWord' }
+    )
+}
+
+# ------------------------------------------------------------
+# Apps: nivel 'seguro' entra no automatico, 'opcional' so' item
+# a item, 'nunca' nunca sai sem o usuario pedir explicitamente.
+# ------------------------------------------------------------
+function Tabela-Apps {
+    @(
+        # seguros
+        [pscustomobject]@{ Id='Clipchamp.Clipchamp';                      Nome='Clipchamp (editor de vídeo)';   Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.3DBuilder';                      Nome='3D Builder';                    Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Microsoft3DViewer';              Nome='Visualizador 3D';               Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Print3D';                        Nome='Impressão 3D';                  Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.549981C3F5F10';                  Nome='Cortana';                       Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingFinance';                    Nome='Bing Finanças';                 Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingFoodAndDrink';               Nome='Bing Culinária';                Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingHealthAndFitness';           Nome='Bing Saúde';                    Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingNews';                       Nome='Notícias (Bing)';               Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingSports';                     Nome='Bing Esportes';                 Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingTranslator';                 Nome='Tradutor Bing';                 Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingTravel';                     Nome='Bing Viagens';                  Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingWeather';                    Nome='Clima (Bing)';                  Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.BingSearch';                     Nome='Busca web do Bing no Iniciar';  Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Copilot';                        Nome='Copilot';                       Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Windows.Copilot';                Nome='Copilot (componente)';          Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Windows.AIHub';                  Nome='AI Hub';                        Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.PCManager';                      Nome='Microsoft PC Manager';          Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Getstarted';                     Nome='Introdução ao Windows';         Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Messaging';                      Nome='Mensagens';                     Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.MicrosoftOfficeHub';             Nome='Office Hub (propaganda)';       Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.MicrosoftSolitaireCollection';   Nome='Paciência (com anúncios)';      Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.MixedReality.Portal';            Nome='Realidade Misturada';           Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.NetworkSpeedTest';               Nome='Teste de Velocidade';           Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.News';                           Nome='Microsoft Notícias';            Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Office.Sway';                    Nome='Sway';                          Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.OneConnect';                     Nome='Mobile Plans';                  Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.SkypeApp';                       Nome='Skype';                         Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Wallet';                         Nome='Carteira';                      Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Whiteboard';                     Nome='Quadro de Comunicações';        Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.MicrosoftJournal';               Nome='Journal';                       Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.WindowsFeedbackHub';             Nome='Hub de Comentários';            Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.WindowsMaps';                    Nome='Mapas';                         Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.ZuneVideo';                      Nome='Filmes e TV';                   Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.PowerAutomateDesktop';           Nome='Power Automate';                Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Windows.DevHome';                Nome='Dev Home';                      Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Windows.Ai.Copilot.Provider';    Nome='Provedor do Copilot';           Nivel='seguro' }
+        [pscustomobject]@{ Id='MicrosoftWindows.Client.WebExperience';    Nome='Widgets';                       Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.WidgetsPlatformRuntime';         Nome='Runtime dos Widgets';           Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.LinkedIn';                       Nome='LinkedIn';                      Nivel='seguro' }
+        [pscustomobject]@{ Id='Microsoft.Family';                         Nome='Segurança da Família';          Nivel='seguro' }
+        [pscustomobject]@{ Id='MSTeams';                                  Nome='Teams (pessoal)';               Nivel='seguro'; Condicao='sem-office' }
+        [pscustomobject]@{ Id='MicrosoftTeams';                           Nome='Teams (pessoal, antigo)';       Nivel='seguro'; Condicao='sem-office' }
+
+        # dependem de jogo
+        [pscustomobject]@{ Id='Microsoft.GamingApp';                      Nome='Xbox (app)';                    Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Id='Microsoft.XboxApp';                        Nome='Xbox Console Companion';        Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Id='Microsoft.XboxGameOverlay';                Nome='Game Bar (overlay)';            Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Id='Microsoft.XboxGamingOverlay';              Nome='Game Bar';                      Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Id='Microsoft.XboxDevices';                    Nome='Acessórios Xbox';               Nivel='seguro'; Condicao='sem-jogos' }
+
+        # opcionais: uteis para muita gente, so' item a item
+        [pscustomobject]@{ Id='Microsoft.OutlookForWindows';              Nome='Novo Outlook';                  Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.Office.OneNote';                 Nome='OneNote (UWP)';                 Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.People';                         Nome='Pessoas';                       Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.Todos';                          Nome='Microsoft To Do';               Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.YourPhone';                      Nome='Vínculo com o Celular';         Nivel='opcional' }
+        [pscustomobject]@{ Id='MicrosoftWindows.CrossDevice';             Nome='Entre Dispositivos';            Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.WindowsAlarms';                  Nome='Alarmes e Relógio';             Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.WindowsCamera';                  Nome='Câmera';                        Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.WindowsSoundRecorder';           Nome='Gravador de Som';               Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.MicrosoftStickyNotes';           Nome='Notas Autoadesivas';            Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.ZuneMusic';                      Nome='Windows Media Player';          Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.Windows.Photos';                 Nome='Fotos';                         Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.Paint';                          Nome='Paint';                         Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.WindowsNotepad';                 Nome='Bloco de Notas';                Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.WindowsCalculator';              Nome='Calculadora';                   Nivel='opcional' }
+        [pscustomobject]@{ Id='Microsoft.ScreenSketch';                   Nome='Ferramenta de Captura';         Nivel='opcional' }
+        [pscustomobject]@{ Id='MicrosoftCorporationII.QuickAssist';       Nome='Assistência Rápida';            Nivel='opcional' }
+
+        # nunca: quebram algo ou nao reinstalam
+        [pscustomobject]@{ Id='Microsoft.WindowsStore';                   Nome='Microsoft Store';               Nivel='nunca'; Aviso='sem ela não dá para reinstalar nada' }
+        [pscustomobject]@{ Id='Microsoft.WindowsTerminal';                Nome='Terminal';                      Nivel='nunca'; Aviso='é onde este programa roda' }
+        [pscustomobject]@{ Id='Microsoft.Edge';                           Nome='Edge';                          Nivel='nunca'; Aviso='quebra o Windows Sandbox e outros apps' }
+        [pscustomobject]@{ Id='Microsoft.GetHelp';                        Nome='Obter Ajuda';                   Nivel='nunca'; Aviso='usado pelas soluções de problemas do Windows' }
+        [pscustomobject]@{ Id='Microsoft.Xbox.TCUI';                      Nome='Xbox TCUI';                     Nivel='nunca'; Aviso='vários jogos dependem dele' }
+        [pscustomobject]@{ Id='Microsoft.XboxIdentityProvider';           Nome='Identidade Xbox';               Nivel='nunca'; Aviso='login de jogos para de funcionar' }
+        [pscustomobject]@{ Id='Microsoft.XboxSpeechToTextOverlay';        Nome='Legendas do Xbox';              Nivel='nunca'; Aviso='não reinstala' }
+    )
+}
+
+# ------------------------------------------------------------
+# Servicos: sempre para Manual, nunca Desativado.
+# BITS, Spooler, WerSvc e tudo de rede/audio/Defender ficam fora
+# de proposito: quebram Windows Update, impressao e diagnostico.
+# ------------------------------------------------------------
+function Tabela-Servicos {
+    @(
+        [pscustomobject]@{ Nome='DiagTrack';        Desc='Telemetria (Experiências do Usuário Conectado)'; Nivel='seguro' }
+        [pscustomobject]@{ Nome='dmwappushservice'; Desc='Encaminhamento de mensagens WAP (telemetria)';   Nivel='seguro' }
+        [pscustomobject]@{ Nome='MapsBroker';       Desc='Mapas Offline';                                  Nivel='seguro' }
+        [pscustomobject]@{ Nome='RetailDemo';       Desc='Modo demonstração de loja';                      Nivel='seguro' }
+        [pscustomobject]@{ Nome='WpcMonSvc';        Desc='Controle dos Pais';                              Nivel='seguro' }
+        [pscustomobject]@{ Nome='WSAIFabricSvc';    Desc='Serviço de IA do Windows';                       Nivel='seguro' }
+        [pscustomobject]@{ Nome='XblAuthManager';   Desc='Autenticação Xbox Live';   Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Nome='XblGameSave';      Desc='Salvamento Xbox Live';     Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Nome='XboxGipSvc';       Desc='Acessórios Xbox';          Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Nome='XboxNetApiSvc';    Desc='Rede Xbox Live';           Nivel='seguro'; Condicao='sem-jogos' }
+        [pscustomobject]@{ Nome='WSearch';          Desc='Indexação do Windows (a busca do Iniciar piora)'; Nivel='opcional'; Condicao='tem-everything' }
+        [pscustomobject]@{ Nome='SysMain';          Desc='Superfetch (ganho discutível em SSD)';            Nivel='opcional' }
+    )
+}
+
+function Tabela-Tarefas {
+    @(
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Application Experience\';                  Nome='Microsoft Compatibility Appraiser' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Application Experience\';                  Nome='Microsoft Compatibility Appraiser Exp' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Application Experience\';                  Nome='ProgramDataUpdater' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Application Experience\';                  Nome='MareBackup' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Customer Experience Improvement Program\'; Nome='Consolidator' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Customer Experience Improvement Program\'; Nome='UsbCeip' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Customer Experience Improvement Program\'; Nome='KernelCeipTask' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Feedback\Siuf\';                           Nome='DmClient' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Feedback\Siuf\';                           Nome='DmClientOnScenarioDownload' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Windows Error Reporting\';                 Nome='QueueReporting' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Maps\';                                    Nome='MapsToastTask' }
+        [pscustomobject]@{ Caminho='\Microsoft\Windows\Autochk\';                                 Nome='Proxy' }
+    )
+}
+
+# Aplica a regra de perfil: devolve so' o que cabe nesta maquina
+function Filtrar-Por-Perfil($itens, $perfil) {
+    $r = @()
+    foreach ($i in $itens) {
+        $c = if ($i.PSObject.Properties['Condicao']) { "$($i.Condicao)" } else { '' }
+        switch ($c) {
+            'sem-jogos'      { if (-not ($perfil.Jogos -or $perfil.GpuDedicada)) { $r += $i } }
+            'sem-office'     { if (-not $perfil.Office) { $r += $i } }
+            'tem-everything' { if ($perfil.Everything) { $r += $i } }
+            default          { $r += $i }
+        }
+    }
+    return @($r)
+}
+
+# ------------------------------------------------------------
+# Apps instalados
+# ------------------------------------------------------------
+function Apps-Presentes($tabela) {
+    $inst = @{}
+    try { foreach ($a in @(Get-AppxPackage -AllUsers -ErrorAction Stop)) { $inst["$($a.Name)"] = $true } }
+    catch {
+        Write-Host "[!] Não li os apps de todos os usuários ($($_.Exception.Message)). Tentando só o atual." -ForegroundColor DarkGray
+        try { foreach ($a in @(Get-AppxPackage -ErrorAction Stop)) { $inst["$($a.Name)"] = $true } }
+        catch { Write-Host "[ERRO] Não consegui listar os apps: $($_.Exception.Message)" -ForegroundColor Red; return @() }
+    }
+    $prov = @{}
+    try { foreach ($p in @(Get-AppxProvisionedPackage -Online -ErrorAction Stop)) { $prov["$($p.DisplayName)"] = $p.PackageName } }
+    catch { Write-Host "[!] Não li os pacotes provisionados: $($_.Exception.Message)" -ForegroundColor DarkGray }
+
+    $r = @()
+    foreach ($t in $tabela) {
+        $temUsuario = $inst.ContainsKey($t.Id)
+        $temProv    = $prov.ContainsKey($t.Id)
+        if (-not $temUsuario -and -not $temProv) { continue }
+        $r += [pscustomobject]@{
+            Id = $t.Id; Nome = $t.Nome; Nivel = $t.Nivel
+            Aviso = $(if ($t.PSObject.Properties['Aviso']) { $t.Aviso } else { '' })
+            Condicao = $(if ($t.PSObject.Properties['Condicao']) { $t.Condicao } else { '' })
+            NoUsuario = $temUsuario; Provisionado = $temProv
+            PacoteProv = $(if ($temProv) { $prov[$t.Id] } else { '' })
+        }
+    }
+    return @($r)
+}
+
+function Remover-App($app) {
+    $fezUsuario = $false; $fezProv = $false
+    if ($app.NoUsuario) {
+        try {
+            Get-AppxPackage -AllUsers -Name $app.Id -ErrorAction Stop | Remove-AppxPackage -AllUsers -ErrorAction Stop
+            $fezUsuario = $true
+        } catch {
+            Write-Host ("  [ERRO] {0}: {1}" -f $app.Nome, $_.Exception.Message) -ForegroundColor Red
+        }
+    }
+    # Sem tirar o provisionado, o app volta para o proximo usuario
+    if ($app.Provisionado -and $app.PacoteProv) {
+        try {
+            Remove-AppxProvisionedPackage -Online -PackageName $app.PacoteProv -ErrorAction Stop | Out-Null
+            $fezProv = $true
+        } catch {
+            Write-Host ("  [!] {0}: o provisionado não saiu ({1})" -f $app.Nome, $_.Exception.Message) -ForegroundColor Yellow
+        }
+    }
+    if (-not $fezUsuario -and -not $fezProv) { return $null }
+    return [pscustomobject]@{ tipo='app'; id=$app.Id; nome=$app.Nome; usuario=$fezUsuario; provisionado=$fezProv }
+}
+
+# ------------------------------------------------------------
+# Serviços e tarefas
+# ------------------------------------------------------------
+function Servicos-Presentes($tabela) {
+    $r = @()
+    foreach ($t in $tabela) {
+        # Servico que nao existe nesta edicao volta vazio, sem erro: o catch
+        # aqui so' dispara em problema de verdade, entao mostra o motivo.
+        $s = $null
+        try { $s = Get-CimInstance Win32_Service -Filter "Name='$($t.Nome)'" -ErrorAction Stop }
+        catch { Write-Host ("[!] Não consegui consultar o serviço {0}: {1}" -f $t.Nome, $_.Exception.Message) -ForegroundColor DarkGray }
+        if (-not $s) { continue }
+        $r += [pscustomobject]@{
+            Nome = $t.Nome; Desc = $t.Desc; Nivel = $t.Nivel
+            Condicao = $(if ($t.PSObject.Properties['Condicao']) { $t.Condicao } else { '' })
+            Inicio = "$($s.StartMode)"; Estado = "$($s.State)"
+            JaOk = ("$($s.StartMode)" -eq 'Manual' -or "$($s.StartMode)" -eq 'Disabled')
+        }
+    }
+    return @($r)
+}
+
+function Ajustar-Servico($svc) {
+    try {
+        Set-Service -Name $svc.Nome -StartupType Manual -ErrorAction Stop
+        return [pscustomobject]@{ tipo='servico'; nome=$svc.Nome; inicioAntes=$svc.Inicio; inicioDepois='Manual' }
+    } catch {
+        Write-Host ("  [ERRO] {0}: {1}" -f $svc.Nome, $_.Exception.Message) -ForegroundColor Red
+        return $null
+    }
+}
+
+function Tarefas-Presentes($tabela) {
+    $r = @()
+    foreach ($t in $tabela) {
+        # Aqui e' diferente dos servicos: tarefa inexistente LANCA excecao, e
+        # isso e' o caso normal (a lista cobre varias versoes do Windows).
+        # Por isso o catch segue em frente sem reclamar.
+        $x = $null
+        try { $x = Get-ScheduledTask -TaskPath $t.Caminho -TaskName $t.Nome -ErrorAction Stop } catch { continue }
+        if (-not $x) { continue }
+        $r += [pscustomobject]@{ Caminho=$t.Caminho; Nome=$t.Nome; Estado="$($x.State)"; JaOk=("$($x.State)" -eq 'Disabled') }
+    }
+    return @($r)
+}
+
+function Desligar-Tarefa($tar) {
+    try {
+        Disable-ScheduledTask -TaskPath $tar.Caminho -TaskName $tar.Nome -ErrorAction Stop | Out-Null
+        return [pscustomobject]@{ tipo='tarefa'; caminho=$tar.Caminho; nome=$tar.Nome; estadoAntes=$tar.Estado }
+    } catch {
+        Write-Host ("  [ERRO] {0}: {1}" -f $tar.Nome, $_.Exception.Message) -ForegroundColor Red
+        return $null
+    }
+}
+
+# ------------------------------------------------------------
+# Prévias
+# ------------------------------------------------------------
+function Previa-Tweaks($itens, [string]$titulo) {
+    Titulo "Prévia: $titulo"
+    $mudam = @(); $jaOk = @()
+    foreach ($i in $itens) {
+        $atual = Ler-Valor $i.Caminho $i.Chave
+        if ($atual.Existe -and "$($atual.Valor)" -eq "$($i.Valor)") { $jaOk += $i } else { $mudam += $i }
+    }
+    foreach ($i in $mudam) { Write-Host ("  [ ] {0}" -f $i.Nome) -ForegroundColor White }
+    foreach ($i in $jaOk)  { Write-Host ("  [ok] {0}  (já está assim)" -f $i.Nome) -ForegroundColor DarkGray }
+    Write-Host ''
+    Write-Host ("A mudar: {0}   |   já corretos: {1}" -f $mudam.Count, $jaOk.Count) -ForegroundColor Cyan
+    return @($mudam)
+}
+
+function Previa-Apps($apps) {
+    Titulo 'Prévia: apps a remover'
+    $seguros = @($apps | Where-Object { $_.Nivel -eq 'seguro' })
+    foreach ($a in $seguros) {
+        $onde = @(); if ($a.NoUsuario) { $onde += 'instalado' }; if ($a.Provisionado) { $onde += 'provisionado' }
+        Write-Host ("  [ ] {0,-36} {1}" -f $a.Nome, ($onde -join ' + ')) -ForegroundColor White
+    }
+    Write-Host ''
+    Write-Host ("Total: {0} app(s)" -f $seguros.Count) -ForegroundColor Cyan
+    Write-Host 'App removido NÃO volta pelo desfazer: só reinstalando pela Microsoft Store.' -ForegroundColor Yellow
+    return @($seguros)
+}
+
+function Previa-Servicos($svcs) {
+    Titulo 'Prévia: serviços para Manual'
+    $mudam = @($svcs | Where-Object { -not $_.JaOk })
+    foreach ($s in $mudam) { Write-Host ("  [ ] {0,-20} {1}   (hoje: {2})" -f $s.Nome, $s.Desc, $s.Inicio) -ForegroundColor White }
+    foreach ($s in @($svcs | Where-Object { $_.JaOk })) { Write-Host ("  [ok] {0,-20} já está em {1}" -f $s.Nome, $s.Inicio) -ForegroundColor DarkGray }
+    Write-Host ''
+    Write-Host 'Manual, não Desativado: o serviço não sobe no boot, mas ainda liga se algo precisar.' -ForegroundColor DarkGray
+    return @($mudam)
+}
+
+function Previa-Tarefas($tars) {
+    Titulo 'Prévia: tarefas de telemetria a desabilitar'
+    $mudam = @($tars | Where-Object { -not $_.JaOk })
+    foreach ($t in $mudam) { Write-Host ("  [ ] {0}{1}" -f $t.Caminho, $t.Nome) -ForegroundColor White }
+    foreach ($t in @($tars | Where-Object { $_.JaOk })) { Write-Host ("  [ok] {0} já desabilitada" -f $t.Nome) -ForegroundColor DarkGray }
+    Write-Host ''
+    Write-Host ("A desabilitar: {0}" -f $mudam.Count) -ForegroundColor Cyan
+    return @($mudam)
+}
+
+# ------------------------------------------------------------
+# Aplicação
+# ------------------------------------------------------------
+function Aplicar-Tweaks($itens) {
+    $acoes = @()
+    foreach ($i in $itens) {
+        $a = Escrever-Valor $i.Caminho $i.Chave $i.Valor $i.Tipo
+        if ($a) { $acoes += $a; Write-Host ("  [OK] {0}" -f $i.Nome) -ForegroundColor Green }
+    }
+    return @($acoes)
+}
+
+function Aplicar-Apps($apps) {
+    $acoes = @(); $n = 0; $tot = @($apps).Count
+    foreach ($a in $apps) {
+        $n++
+        Write-Progress -Activity 'Removendo apps' -Status $a.Nome -PercentComplete ([int](100 * $n / $tot))
+        $r = Remover-App $a
+        if ($r) { $acoes += $r; Write-Host ("  [OK] {0}" -f $a.Nome) -ForegroundColor Green }
+    }
+    Write-Progress -Activity 'Removendo apps' -Completed
+    return @($acoes)
+}
+
+function Aplicar-Servicos($svcs) {
+    $acoes = @()
+    foreach ($s in $svcs) {
+        $a = Ajustar-Servico $s
+        if ($a) { $acoes += $a; Write-Host ("  [OK] {0} -> Manual" -f $s.Nome) -ForegroundColor Green }
+    }
+    return @($acoes)
+}
+
+function Aplicar-Tarefas($tars) {
+    $acoes = @()
+    foreach ($t in $tars) {
+        $a = Desligar-Tarefa $t
+        if ($a) { $acoes += $a; Write-Host ("  [OK] {0}" -f $t.Nome) -ForegroundColor Green }
+    }
+    return @($acoes)
+}
+
+# ------------------------------------------------------------
+# Desfazer
+# ------------------------------------------------------------
+function Desfazer-Tudo($diario) {
+    $execs = @($diario.execucoes)
+    if ($execs.Count -eq 0) {
+        Write-Host '[i] O diário está vazio: este programa ainda não alterou nada nesta máquina.' -ForegroundColor Cyan
+        return $false
+    }
+    $todas = @()
+    foreach ($e in $execs) { $todas += @($e.acoes) }
+
+    $reg  = @($todas | Where-Object { $_.tipo -eq 'registro' })
+    $svc  = @($todas | Where-Object { $_.tipo -eq 'servico' })
+    $tar  = @($todas | Where-Object { $_.tipo -eq 'tarefa' })
+    $apps = @($todas | Where-Object { $_.tipo -eq 'app' })
+
+    Titulo 'Prévia: o que será revertido'
+    Write-Host ("  Execuções registradas: {0}" -f $execs.Count) -ForegroundColor White
+    Write-Host ("  Chaves de registro:    {0}" -f $reg.Count) -ForegroundColor White
+    Write-Host ("  Serviços:              {0}" -f $svc.Count) -ForegroundColor White
+    Write-Host ("  Tarefas:               {0}" -f $tar.Count) -ForegroundColor White
+    Write-Host ("  Apps removidos:        {0}   (estes NÃO voltam por aqui)" -f $apps.Count) -ForegroundColor Yellow
+    Write-Host ''
+    if (-not (Confirmar 'Reverter tudo?' $false)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; return $false }
+
+    Titulo 'Revertendo'
+    $ok = 0; $err = 0
+    # Ordem inversa: o ultimo valor gravado e' o primeiro a voltar
+    for ($i = $todas.Count - 1; $i -ge 0; $i--) {
+        $a = $todas[$i]
+        switch ($a.tipo) {
+            'registro' { if (Reverter-Valor $a) { $ok++ } else { $err++ } }
+            'servico' {
+                try { Set-Service -Name $a.nome -StartupType $a.inicioAntes -ErrorAction Stop; $ok++ }
+                catch { $err++; Write-Host ("  [ERRO] serviço {0}: {1}" -f $a.nome, $_.Exception.Message) -ForegroundColor Red }
+            }
+            'tarefa' {
+                try { Enable-ScheduledTask -TaskPath $a.caminho -TaskName $a.nome -ErrorAction Stop | Out-Null; $ok++ }
+                catch { $err++; Write-Host ("  [ERRO] tarefa {0}: {1}" -f $a.nome, $_.Exception.Message) -ForegroundColor Red }
+            }
+        }
+    }
+    Write-Host ''
+    Write-Host ("Revertidos: {0}" -f $ok) -ForegroundColor Green
+    if ($err -gt 0) { Write-Host ("Falharam:   {0}" -f $err) -ForegroundColor Red }
+
+    if ($apps.Count -gt 0) {
+        Write-Host ''
+        Write-Host 'Estes apps foram removidos e precisam ser reinstalados pela Store:' -ForegroundColor Yellow
+        foreach ($a in ($apps | Sort-Object nome -Unique)) { Write-Host ("  - {0}" -f $a.nome) -ForegroundColor Gray }
+        Write-Host ''
+        if (Confirmar 'Abrir a Microsoft Store?' $false) { Abrir-Url 'ms-windows-store://home' }
+    }
+    return $true
+}
+
+# ------------------------------------------------------------
+# Relatório
+# ------------------------------------------------------------
+function Salvar-Relatorio([string]$arquivo, $perfil, $apps, $svcs, $tars, $tweaks) {
+    $l = @()
+    $l += "Relatorio do Debloat - $env:COMPUTERNAME - $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
+    $l += "Desenvolvido por Pablo Murad - 2026"
+    $l += ''
+    $l += "RAM: $($perfil.RamGB) GB | GPU dedicada: $($perfil.GpuDedicada) | Jogos: $($perfil.Jogos) | Everything: $($perfil.Everything) | Office: $($perfil.Office)"
+    $l += ''
+    $l += '--- APPS DA MICROSOFT PRESENTES ---'
+    foreach ($nivel in @('seguro','opcional','nunca')) {
+        $g = @($apps | Where-Object { $_.Nivel -eq $nivel })
+        if ($g.Count -eq 0) { continue }
+        $l += "[$nivel]"
+        foreach ($a in $g) { $l += "  $($a.Id)  -  $($a.Nome)" }
+    }
+    $l += ''
+    $l += '--- SERVICOS ---'
+    foreach ($s in $svcs) { $l += ("  {0,-20} {1,-10} {2}" -f $s.Nome, $s.Inicio, $s.Desc) }
+    $l += ''
+    $l += '--- TAREFAS DE TELEMETRIA ---'
+    foreach ($t in $tars) { $l += ("  {0,-10} {1}{2}" -f $t.Estado, $t.Caminho, $t.Nome) }
+    $l += ''
+    $l += '--- CONFIGURACOES ---'
+    foreach ($i in $tweaks) {
+        $v = Ler-Valor $i.Caminho $i.Chave
+        $estado = if ($v.Existe -and "$($v.Valor)" -eq "$($i.Valor)") { 'ok    ' } else { 'aberto' }
+        $l += ("  [{0}] {1}" -f $estado, $i.Nome)
+    }
+    try {
+        Set-Content -LiteralPath $arquivo -Value $l -Encoding UTF8
+        Write-Host ''
+        Write-Host ("[OK] Relatório salvo: {0}" -f $arquivo) -ForegroundColor Green
+    } catch { Write-Host "[!] Não salvei o relatório: $($_.Exception.Message)" -ForegroundColor Yellow }
+}
+
+# ------------------------------------------------------------
+# Início
+# ------------------------------------------------------------
+Abrir 'Debloat'
+Write-Host ''
+Write-Host 'O ganho de velocidade é pequeno e honesto: de 200 a 800 MB de RAM e' -ForegroundColor Gray
+Write-Host 'de 1 a 3 segundos de boot. O que muda mesmo é tirar irritação e telemetria.' -ForegroundColor Gray
+Write-Host ''
+
+$perfil = Detectar-Perfil
+Mostrar-Perfil $perfil
+
+$config = Carregar-Config
+$padrao = if ($config -and $config.UltimaPasta) { $config.UltimaPasta } else { Join-Path $env:USERPROFILE 'Downloads\Debloat' }
+$raiz   = Pedir-Saida $padrao
+Salvar-Config ([pscustomobject]@{ UltimaPasta = $raiz })
+
+$pastaPc = Join-Path $raiz $env:COMPUTERNAME
+try { New-Item -ItemType Directory -Path $pastaPc -Force | Out-Null }
+catch { Write-Host "[ERRO] Não criei a pasta desta máquina: $($_.Exception.Message)" -ForegroundColor Red; Pausar; exit 1 }
+
+$diarioArq = Join-Path $configDir 'desfazer.json'
+$tabTweaks = Filtrar-Por-Perfil (Tabela-Tweaks)   $perfil
+$tabApps   = Filtrar-Por-Perfil (Tabela-Apps)     $perfil
+$tabSvcs   = Filtrar-Por-Perfil (Tabela-Servicos) $perfil
+$tabTars   = Tabela-Tarefas
+
+# Guarda se ja criamos ponto de restauracao nesta execucao
+$pontoFeito = $false
+
+while ($true) {
+    Titulo 'DEBLOAT'
+    Write-Host ("  {0}  |  relatórios: {1}" -f $env:COMPUTERNAME, $raiz) -ForegroundColor DarkGray
+    Write-Host ''
+    Write-Host '> [1] Diagnóstico: o que dá para limpar nesta máquina' -ForegroundColor White
+    Write-Host '  [2] Fazer tudo o que é recomendado' -ForegroundColor Gray
+    Write-Host '  [3] Apps da Microsoft' -ForegroundColor Gray
+    Write-Host '  [4] Privacidade e telemetria' -ForegroundColor Gray
+    Write-Host '  [5] Copilot, Recall e IA' -ForegroundColor Gray
+    Write-Host '  [6] Anúncios, sugestões e irritações' -ForegroundColor Gray
+    Write-Host '  [7] Serviços em segundo plano' -ForegroundColor Gray
+    Write-Host '  [8] Tarefas agendadas de telemetria' -ForegroundColor Gray
+    Write-Host '  [9] Desfazer o que este programa fez' -ForegroundColor Gray
+    Write-Host ''
+    Write-Host '  [0] Sair' -ForegroundColor DarkGray
+    Write-Host 'Enter = usar a opção padrão [1]' -ForegroundColor DarkGray
+    $op = Read-Host 'Opção (0-9)'
+    if ([string]::IsNullOrWhiteSpace($op)) { $op = '1' }
+    $op = $op.Trim()
+    if ($op -eq '0') { break }
+
+    $diario = Carregar-Diario $diarioArq
+    $acoes  = @()
+
+    switch ($op) {
+
+        '1' {
+            Titulo 'Diagnóstico'
+            Write-Host 'Lendo apps, serviços, tarefas e configurações...' -ForegroundColor Gray
+            $apps = Apps-Presentes $tabApps
+            $svcs = Servicos-Presentes $tabSvcs
+            $tars = Tarefas-Presentes $tabTars
+
+            Write-Host ''
+            foreach ($nivel in @('seguro','opcional','nunca')) {
+                $g = @($apps | Where-Object { $_.Nivel -eq $nivel })
+                if ($g.Count -eq 0) { continue }
+                $rot = switch ($nivel) { 'seguro' {'Seguros de remover'} 'opcional' {'Opcionais (você decide)'} default {'Não recomendo remover'} }
+                $cor = switch ($nivel) { 'seguro' {'Green'} 'opcional' {'Yellow'} default {'Red'} }
+                Write-Host ("  {0}  ({1})" -f $rot, $g.Count) -ForegroundColor $cor
+                foreach ($a in $g) {
+                    $av = if ($a.Aviso) { "  <- $($a.Aviso)" } else { '' }
+                    Write-Host ("     {0}{1}" -f $a.Nome, $av) -ForegroundColor Gray
+                }
+                Write-Host ''
+            }
+            Write-Host ("  Serviços que dá para aliviar: {0} de {1}" -f @($svcs | Where-Object { -not $_.JaOk }).Count, $svcs.Count) -ForegroundColor White
+            Write-Host ("  Tarefas de telemetria ativas: {0} de {1}" -f @($tars | Where-Object { -not $_.JaOk }).Count, $tars.Count) -ForegroundColor White
+            $abertos = 0
+            foreach ($i in $tabTweaks) { $v = Ler-Valor $i.Caminho $i.Chave; if (-not ($v.Existe -and "$($v.Valor)" -eq "$($i.Valor)")) { $abertos++ } }
+            Write-Host ("  Configurações fora do ideal:  {0} de {1}" -f $abertos, @($tabTweaks).Count) -ForegroundColor White
+
+            # O Windows Update reinstala o que foi removido: avisa o que voltou
+            $jaRemovidos = @()
+            foreach ($e in @($diario.execucoes)) { foreach ($a in @($e.acoes)) { if ($a.tipo -eq 'app') { $jaRemovidos += "$($a.id)" } } }
+            $voltaram = @($apps | Where-Object { $jaRemovidos -contains $_.Id })
+            if ($voltaram.Count -gt 0) {
+                Write-Host ''
+                Write-Host ("[!] {0} app(s) que você já removeu VOLTARAM (atualização do Windows):" -f $voltaram.Count) -ForegroundColor Yellow
+                foreach ($a in $voltaram) { Write-Host ("     {0}" -f $a.Nome) -ForegroundColor Gray }
+            }
+            Write-Host ''
+            Write-Host 'Para limpar arquivos temporários e a lixeira, use o Úteis\limpeza-segura.' -ForegroundColor DarkGray
+            Salvar-Relatorio (Join-Path $pastaPc ("relatorio-{0}.txt" -f (Get-Date -Format 'yyyy-MM-dd'))) $perfil $apps $svcs $tars $tabTweaks
+            Pausar
+        }
+
+        '2' {
+            Titulo 'Tudo o que é recomendado'
+            $apps = @((Apps-Presentes $tabApps) | Where-Object { $_.Nivel -eq 'seguro' })
+            $svcs = @((Servicos-Presentes $tabSvcs) | Where-Object { $_.Nivel -eq 'seguro' -and -not $_.JaOk })
+            $tars = @((Tarefas-Presentes $tabTars) | Where-Object { -not $_.JaOk })
+            $tw   = Previa-Tweaks $tabTweaks 'privacidade, IA e irritações'
+            Write-Host ''
+            Write-Host ("Apps a remover:      {0}" -f $apps.Count) -ForegroundColor White
+            Write-Host ("Serviços a aliviar:  {0}" -f $svcs.Count) -ForegroundColor White
+            Write-Host ("Tarefas a desligar:  {0}" -f $tars.Count) -ForegroundColor White
+            Write-Host ''
+            Write-Host 'App removido NÃO volta pelo desfazer. O resto volta.' -ForegroundColor Yellow
+            Write-Host ''
+            if (-not (Confirmar 'Aplicar tudo isso?' $false)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            if (-not $pontoFeito) { [void](Criar-Ponto-Restauracao); $pontoFeito = $true }
+            Titulo 'Aplicando'
+            $acoes += Aplicar-Tweaks   $tw
+            $acoes += Aplicar-Apps     $apps
+            $acoes += Aplicar-Servicos $svcs
+            $acoes += Aplicar-Tarefas  $tars
+        }
+
+        '3' {
+            Titulo 'Apps da Microsoft'
+            $apps = Apps-Presentes $tabApps
+            $alvo = Previa-Apps $apps
+            if ($alvo.Count -eq 0) { Write-Host '[OK] Nada seguro a remover: já está limpo.' -ForegroundColor Green; Pausar; continue }
+            Write-Host ''
+            if (-not (Confirmar 'Remover esses apps?' $false)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            if (-not $pontoFeito) { [void](Criar-Ponto-Restauracao); $pontoFeito = $true }
+            Titulo 'Removendo'
+            $acoes += Aplicar-Apps $alvo
+        }
+
+        { $_ -in @('4','5','6') } {
+            $area = switch ($op) { '4' {'privacidade'} '5' {'ia'} default {'irritacao'} }
+            $rot  = switch ($op) { '4' {'Privacidade e telemetria'} '5' {'Copilot, Recall e IA'} default {'Anúncios, sugestões e irritações'} }
+            $itens = @($tabTweaks | Where-Object { $_.Area -eq $area })
+            $mudam = Previa-Tweaks $itens $rot
+            if ($mudam.Count -eq 0) { Write-Host ''; Write-Host '[OK] Já está tudo do jeito certo.' -ForegroundColor Green; Pausar; continue }
+            Write-Host ''
+            if (-not (Confirmar 'Aplicar?' $true)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            if (-not $pontoFeito) { [void](Criar-Ponto-Restauracao); $pontoFeito = $true }
+            Titulo 'Aplicando'
+            $acoes += Aplicar-Tweaks $mudam
+        }
+
+        '7' {
+            Titulo 'Serviços em segundo plano'
+            if ($perfil.RamGB -ge 32) {
+                Write-Host ("Com {0} GB de RAM isso quase não muda nada. Faça só se quiser padronizar." -f $perfil.RamGB) -ForegroundColor DarkGray
+            }
+            $svcs = Servicos-Presentes $tabSvcs
+            $mudam = Previa-Servicos $svcs
+            if ($mudam.Count -eq 0) { Write-Host ''; Write-Host '[OK] Nada a mudar.' -ForegroundColor Green; Pausar; continue }
+            Write-Host ''
+            if (-not (Confirmar 'Passar esses para Manual?' $false)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            if (-not $pontoFeito) { [void](Criar-Ponto-Restauracao); $pontoFeito = $true }
+            Titulo 'Aplicando'
+            $acoes += Aplicar-Servicos $mudam
+        }
+
+        '8' {
+            Titulo 'Tarefas agendadas de telemetria'
+            $tars = Tarefas-Presentes $tabTars
+            $mudam = Previa-Tarefas $tars
+            if ($mudam.Count -eq 0) { Write-Host ''; Write-Host '[OK] Todas já estão desabilitadas.' -ForegroundColor Green; Pausar; continue }
+            Write-Host ''
+            if (-not (Confirmar 'Desabilitar essas tarefas?' $true)) { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; continue }
+            if (-not $pontoFeito) { [void](Criar-Ponto-Restauracao); $pontoFeito = $true }
+            Titulo 'Aplicando'
+            $acoes += Aplicar-Tarefas $mudam
+        }
+
+        '9' {
+            Titulo 'Desfazer'
+            [void](Desfazer-Tudo $diario)
+            # O desfazer zera o diario: o que foi revertido nao deve ser revertido de novo
+            try {
+                $vazio = [pscustomobject]@{ versao=1; maquina=$env:COMPUTERNAME; execucoes=@() }
+                $vazio | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $diarioArq -Encoding UTF8
+            } catch { Write-Host "[!] Não limpei o diário: $($_.Exception.Message)" -ForegroundColor DarkGray }
+            Pausar
+        }
+
+        default { Write-Host '[!] Escolha de 0 a 9.' -ForegroundColor Yellow; Start-Sleep -Seconds 1 }
+    }
+
+    if (@($acoes).Count -gt 0) {
+        Gravar-Diario $diarioArq $diario $acoes $pastaPc
+        Write-Host ''
+        Write-Host ("[OK] {0} alteração(ões) registradas no diário. A opção 9 reverte." -f @($acoes).Count) -ForegroundColor Green
+        Write-Host '[i] Algumas mudanças só aparecem depois de reiniciar o Explorer ou o PC.' -ForegroundColor Cyan
+        Pausar
+    }
+}
+
+Titulo 'Concluído'
+Write-Host ("Relatórios: {0}" -f $pastaPc) -ForegroundColor White
+Write-Host ("Diário do desfazer: {0}" -f $diarioArq) -ForegroundColor White
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
@@ -120,8 +120,8 @@ $raizCanivete = Split-Path -Parent $MyInvocation.MyCommand.Path
 if (-not $raizCanivete) { $raizCanivete = (Get-Location).Path }
 
-$ordem = @('Vídeos','Imagens','Organizar','Úteis')
-$icone = @{ 'Vídeos'='[V]'; 'Imagens'='[I]'; 'Organizar'='[O]'; 'Úteis'='[U]' }
+$ordem = @('Vídeos','Imagens','Organizar','Úteis','Windows')
+$icone = @{ 'Vídeos'='[V]'; 'Imagens'='[I]'; 'Organizar'='[O]'; 'Úteis'='[U]'; 'Windows'='[W]' }
 
 while ($true) {
     Clear-Host
@@ -140,7 +140,9 @@ Write-Host ("  {0} {1}" -f $icone[$cat], $cat.ToUpperInvariant()) -ForegroundColor Cyan         foreach ($c in $cmds) {
             $n++
             $itens += $c
-            $nome = $c.BaseName -replace '-', ' '
+            # Nomes com prefixo de ordem ("01 - drivers") viram "01 drivers":
+            # troca o hifen por espaco e junta os espacos que sobram
+            $nome = ($c.BaseName -replace '-', ' ') -replace '\s{2,}', ' '
             Write-Host ("    {0,2}. {1}" -f $n, $nome) -ForegroundColor Gray
         }
     }
@@ -1,24 +1,26 @@-# Dependências
-
-A maioria dos programas **não precisa de nada**. Os que precisam avisam na tela e mostram o comando de instalação.
-
-## Tabela
-
-| Ferramenta | Para que serve | Como instalar | Usada por |
-|---|---|---|---|
-| **FFmpeg** | Tudo de vídeo e áudio | `winget install Gyan.FFmpeg` | transcodificar, extrair-audio, criar-gif, cortar-video, juntar-videos, marca-dagua-video, info-midia, normalizar-audio, baixar-videos, limpar-metadados |
-| **ImageMagick** | Tudo de imagem | `winget install ImageMagick.ImageMagick` | converter-imagens, marca-dagua, imagens-e-pdf, limpar-metadados, renomear-em-lote (data da foto) |
-| **Ghostscript** | Ler e comprimir PDF | `winget install ArtifexSoftware.GhostScript` | comprimir-pdf, imagens-e-pdf (PDF → imagens) |
-| **yt-dlp** | Baixar de sites | *baixado automaticamente* | baixar-videos |
-| **7-Zip** *(opcional)* | `.rar`, `.7z` e mais velocidade | `winget install 7zip.7zip` | compactar-zip |
-| **smartmontools** *(opcional)* | Detalhes SMART avançados | `winget install smartmontools` | verificar-discos |
-
-## Observações
-
-- **O yt-dlp se instala sozinho.** Se não existir na máquina, o `baixar-videos` baixa a versão oficial mais recente em `%LOCALAPPDATA%\fast-scripts` e ainda a mantém atualizada.
-- **O Ghostscript costuma não ficar no PATH.** Os programas procuram também em `C:\Program Files\gs`, então funciona mesmo assim.
-- **Placa NVIDIA** não é uma dependência, mas é aproveitada automaticamente quando existe (transcodificar e marca-dagua-video).
-- Programas que **não precisam de nada**: organizar-por-tipo, achar-duplicados, limpar-nomes, backup-espelhado, analisar-espaco, verificar-integridade, relatorio-sistema, limpeza-segura, verificar-discos, canivete.
-
----
+# Dependências
+
+A maioria dos programas **não precisa de nada**. Os que precisam avisam na tela e mostram o comando de instalação.
+
+## Tabela
+
+| Ferramenta | Para que serve | Como instalar | Usada por |
+|---|---|---|---|
+| **FFmpeg** | Tudo de vídeo e áudio | `winget install Gyan.FFmpeg` | transcodificar, extrair-audio, criar-gif, cortar-video, juntar-videos, marca-dagua-video, info-midia, normalizar-audio, baixar-videos, limpar-metadados |
+| **ImageMagick** | Tudo de imagem | `winget install ImageMagick.ImageMagick` | converter-imagens, marca-dagua, imagens-e-pdf, limpar-metadados, renomear-em-lote (data da foto) |
+| **Ghostscript** | Ler e comprimir PDF | `winget install ArtifexSoftware.GhostScript` | comprimir-pdf, imagens-e-pdf (PDF → imagens) |
+| **yt-dlp** | Baixar de sites | *baixado automaticamente* | baixar-videos |
+| **7-Zip** *(opcional)* | `.rar`, `.7z` e mais velocidade | `winget install 7zip.7zip` | compactar-zip |
+| **smartmontools** *(opcional)* | Detalhes SMART avançados | `winget install smartmontools` | verificar-discos |
+| **winget** | Instalar a ferramenta do fabricante | *instalado automaticamente* | drivers |
+
+## Observações
+
+- **O yt-dlp se instala sozinho.** Se não existir na máquina, o `baixar-videos` baixa a versão oficial mais recente em `%LOCALAPPDATA%\fast-scripts` e ainda a mantém atualizada.
+- **O Ghostscript costuma não ficar no PATH.** Os programas procuram também em `C:\Program Files\gs`, então funciona mesmo assim.
+- **Placa NVIDIA** não é uma dependência, mas é aproveitada automaticamente quando existe (transcodificar e marca-dagua-video).
+- **O `drivers` se vira sozinho.** Usa o `pnputil` e o `expand` do Windows e, se o winget não existir, baixa o App Installer oficial e registra antes de instalar a ferramenta do fabricante.
+- Programas que **não precisam de nada**: organizar-por-tipo, achar-duplicados, limpar-nomes, backup-espelhado, analisar-espaco, verificar-integridade, relatorio-sistema, limpeza-segura, verificar-discos, drivers, debloat, canivete.
+
+---
 *Desenvolvido por Pablo Murad - 2026*
@@ -39,6 +39,19 @@ - Já baixados são **pulados** automaticamente.
 - Embute capa e informações (título, autor) no arquivo.
 - A cópia do yt-dlp que o programa baixa **se atualiza sozinha** a cada 15 dias.
+- Se o yt-dlp da máquina estiver com mais de 60 dias, o programa **avisa e oferece atualizar** logo ao abrir.
+
+## Problemas comuns
+
+**Falhou no YouTube?** Atualize o yt-dlp — é a causa mais comum. O YouTube muda com frequência e derruba versões antigas. O programa oferece a atualização ao abrir.
+
+**aria2c:** se estiver instalado, é usado para acelerar o download nos outros sites, mas **não no YouTube** — lá o site bloqueia baixadores de múltiplas conexões (o "experimento SABR"), e o download falha com erro de rede. Se mesmo assim algo falhar com o aria2c, o programa **refaz sozinho no modo normal**.
+
+**Aviso "No supported JavaScript runtime":** o YouTube passou a exigir JavaScript para entregar todos os formatos. Instale o Deno para destravar os formatos que faltam:
+
+```
+winget install DenoLand.Deno
+```
 
 ---
 *Desenvolvido por Pablo Murad - 2026*
@@ -0,0 +1,85 @@+# Debloat
+
+Tira o excesso do Windows — apps que você nunca abriu, telemetria, Copilot, Recall, anúncios no Iniciar — mostrando tudo antes e guardando como desfazer.
+
+## Antes de tudo: o que esperar
+
+O ganho de velocidade é pequeno, e vale dizer isso de frente. Os testes publicados mostram de 200 a 800 MB de RAM livre em repouso e de 1 a 3 segundos no boot com SSD. Numa máquina com 32 GB ou mais, isso é ruído.
+
+O que o debloat entrega de verdade é outra coisa: menos irritação, menos telemetria, e cinco máquinas configuradas igual. Se a sua expectativa é ganhar FPS, não é aqui.
+
+## Como abrir
+
+Dê um duplo-clique em `Windows\02 - debloat.cmd` — ou abra o `canivete.cmd` na raiz e escolha pelo número.
+
+## O que precisa
+
+Nada. Usa só o próprio Windows.
+
+## Passo a passo
+
+1. O programa **pede permissão de administrador**.
+2. Ele olha a máquina e ajusta as recomendações ao que encontrou.
+3. Pergunta onde salvar os relatórios, lembrando a última pasta.
+4. Mostra o menu. Toda opção tem prévia e confirmação.
+
+## Ele se adapta à máquina
+
+O programa não aplica a mesma lista em tudo:
+
+| Encontrou | Muda o quê |
+|---|---|
+| Steam, Epic, GOG, Battle.net ou placa dedicada | Xbox e Game Bar saem do automático |
+| Everything (voidtools) | Passa a oferecer aliviar a indexação do Windows |
+| Office instalado | Não mexe em Teams nem OneNote |
+| 32 GB de RAM ou mais | Avisa que mexer em serviço quase não muda nada |
+
+## Opções
+
+| # | Opção | Altera algo? |
+|---|-------|---|
+| 1 | Diagnóstico: o que dá para limpar | Não, só leitura |
+| 2 | Fazer tudo o que é recomendado | Sim, com prévia |
+| 3 | Apps da Microsoft | Sim, com prévia |
+| 4 | Privacidade e telemetria | Sim, com prévia |
+| 5 | Copilot, Recall e IA | Sim, com prévia |
+| 6 | Anúncios, sugestões e irritações | Sim, com prévia |
+| 7 | Serviços em segundo plano | Sim, com prévia |
+| 8 | Tarefas agendadas de telemetria | Sim, com prévia |
+| 9 | Desfazer o que este programa fez | Reverte |
+
+## Os três níveis de app
+
+Os 72 apps que o programa conhece são separados por risco, e só o primeiro nível entra no automático:
+
+- **Seguro** — Bing, Clipchamp, Solitaire, Teams pessoal, Copilot, widgets, 3D Builder e companhia.
+- **Opcional** — Paint, Bloco de Notas, Fotos, Calculadora, Ferramenta de Captura, Câmera, novo Outlook. Muita gente usa; você escolhe um a um.
+- **Nunca** — Store, Terminal, Edge, Obter Ajuda, e três componentes do Xbox. A Store não reinstala nada depois de removida, e as legendas do Xbox não voltam de jeito nenhum.
+
+## Serviços viram Manual, não Desativado
+
+Assim o serviço para de subir no boot, mas ainda liga se algo precisar dele. Ficam de fora de propósito, em qualquer nível: `BITS` (quebra o Windows Update), `Spooler` (quebra impressão), `WerSvc`, e tudo de rede, áudio e Defender.
+
+## Onde salva
+
+```
+<sua pasta>\
+    DESKTOP-PABLO\
+        relatorio-2026-09-20.txt
+        desfazer.json            <- cópia
+```
+
+O diário do desfazer vive em `%APPDATA%\Debloat\desfazer.json`, para funcionar mesmo sem o pendrive por perto. Ele guarda o valor anterior de cada chave, serviço e tarefa alterados.
+
+## Dicas
+
+- **Antes da primeira alteração de cada execução, o programa cria um ponto de restauração.** O Windows só permite um a cada 24 horas; se já existir um de hoje, ele avisa e segue.
+- **O que o desfazer devolve:** chaves de registro, serviços e tarefas. Chave que não existia antes é apagada, chave que existia volta ao valor original.
+- **O que o desfazer NÃO devolve: apps.** Não existe forma honesta de reinstalar um Appx removido sem a Store. O programa lista o que removeu e abre a Store para você.
+- **O Windows Update traz coisa de volta.** Pacotes provisionados voltam em atualizações de recurso. Por isso o programa foi feito para rodar de novo: o diagnóstico compara com o diário e avisa quais apps reapareceram.
+- **Rodar duas vezes não faz mal.** Tudo que já está do jeito certo aparece marcado como `[ok]` e é pulado.
+- Algumas mudanças na barra de tarefas e no Iniciar só aparecem depois de reiniciar o Explorer ou o PC.
+- Para limpar arquivos temporários e a lixeira, use o **[limpeza-segura](limpeza-segura.md)** — este programa não duplica aquilo.
+
+---
+*Desenvolvido por Pablo Murad - 2026*
@@ -0,0 +1,84 @@+# Drivers
+
+Salva os drivers que já funcionam na sua máquina, devolve depois de formatar e busca o que estiver faltando. Feito para as cinco máquinas: cada uma cria a própria pasta, e todas cabem no mesmo pendrive.
+
+## Como abrir
+
+Dê um duplo-clique em `Windows\01 - drivers.cmd` — ou abra o `canivete.cmd` na raiz e escolha pelo número.
+
+## O que precisa
+
+Nada. O programa usa o `pnputil` e o `expand` do próprio Windows, e **instala sozinho** o que mais precisar: se o winget não existir na máquina recém-formatada, ele baixa o App Installer oficial da Microsoft e registra; se a ferramenta do fabricante faltar, ele instala pelo winget antes de usar.
+
+## Passo a passo
+
+1. O programa **pede permissão de administrador**. Sem ela não dá para mexer em driver.
+2. Ele identifica a máquina: fabricante, modelo e placa-mãe.
+3. Pergunta a **pasta dos drivers**, lembrando a última usada. Pode ser um pendrive ou uma pasta de rede.
+4. **Faz o backup do dia**, se ainda não existir.
+5. Mostra o menu e volta para ele ao terminar cada opção.
+
+## Opções
+
+| # | Opção | Tempo |
+|---|-------|-------|
+| 1 | Inventário: o que tem e o que falta | Segundos |
+| 2 | Fazer um novo backup agora | 1 a 5 minutos |
+| 3 | Restaurar drivers de uma pasta | Segundos a minutos |
+| 4 | Restaurar só o driver de rede | Segundos |
+| 5 | Procurar atualizações no Windows Update | 1 a 3 minutos |
+| 6 | Ferramenta do fabricante | Varia |
+
+A opção 6 muda conforme a máquina, e em quase todos os casos **instala em vez de só indicar**:
+
+| Máquina | O que ele faz |
+|---|---|
+| Dell | Instala o Dell Command \| Update e roda `dcu-cli /scan` e `/applyUpdates` |
+| HP | Instala o HP Image Assistant e roda a análise em modo silencioso |
+| Lenovo | Instala e abre o Lenovo System Update |
+| Montado | Mostra a placa-mãe e abre a busca pelos drivers dela |
+
+Depois disso, seja qual for a máquina: se houver **NVIDIA**, oferece instalar o NVIDIA App, que é quem baixa e mantém o driver; se houver **AMD**, abre a página do Adrenalin, porque a AMD não publica no winget; e se houver **Intel**, oferece o Intel Driver & Support Assistant, que cobre chipset, Wi-Fi e vídeo integrado.
+
+## Onde salva
+
+Na pasta que você escolher, separado por máquina:
+
+```
+<sua pasta>\
+    DESKTOP-PABLO\
+        2026-09-20\          <- os drivers exportados
+        inventario.csv       <- todos os dispositivos e versões
+        catalog\             <- o que veio do Microsoft Update Catalog
+    NOTEBOOK-SALA\
+        ...
+```
+
+O backup do dia é feito uma vez só: se você abrir o programa de novo no mesmo dia, ele pula essa etapa. Para forçar um novo, use a opção 2 — ela cria uma pasta com a hora no nome e não sobrescreve nada.
+
+## A ordem que funciona
+
+Antes de formatar, rode o programa e deixe o backup terminar. Guarde a pasta num pendrive.
+
+Depois de formatar, com a máquina sem internet:
+
+1. Opção **4**, apontando para o pendrive. A placa de rede volta em segundos e a internet funciona.
+2. Opção **3**, para o resto dos drivers.
+3. Opção **5**, para pegar o que houver de mais novo.
+4. Opção **6**, se a máquina for de marca ou tiver placa de vídeo dedicada.
+
+## Dicas
+
+- **A prévia sempre aparece antes de instalar.** Você vê os drivers agrupados por categoria, com fabricante e versão, e só então confirma.
+- **Nada é apagado.** O Windows adiciona o driver ao repositório e escolhe o mais adequado entre o que já existia e o novo.
+- **O backup salva o driver, não o aplicativo.** Depois de restaurar, a placa de vídeo funciona, mas o painel da NVIDIA e o console de áudio da Realtek não voltam — esses precisam ser instalados à parte.
+- **Placa dedicada merece o pacote oficial.** Para jogos, use o instalador da NVIDIA ou da AMD; a opção 6 abre a página certa.
+- **O backup é uma fotografia de hoje**, não uma busca por versão nova. Quem busca novidade é a opção 5.
+- Driver exportado de um Windows costuma servir em outro, mas não entre arquiteturas diferentes (32 e 64 bits).
+- Se alguns drivers falharem na restauração, o programa refaz um por um e diz o nome de cada um. Falha aqui costuma ser driver de hardware que não existe nessa máquina.
+- **Restaurar na mesma máquina não é erro.** O que já estava no repositório aparece como "Já existiam", separado dos instalados.
+- Quando algum driver pede reinicialização, o programa avisa no fim.
+- **A busca no Microsoft Update Catalog lê a página da Microsoft.** É a única parte do programa que depende do site de terceiro: se a Microsoft mudar o layout, ela para de achar e o programa avisa com o endereço da busca na tela.
+
+---
+*Desenvolvido por Pablo Murad - 2026*