@@ -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.
-
-
-
-
-
-## 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.
+
+
+
+
+
+## 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*