Projeto: script de limpeza/manutenção
LidoNesta aula, você aprenderá a criar um script de limpeza e manutenção de sistema no PowerShell, cobrindo desde a identificação de arquivos temporários e obsoletos até a remoção segura com proteções como dry-run e logging, seguindo boas práticas de segurança e auditoria.
Nesta aula, vamos colocar em prática os conceitos de PowerShell para desenvolver um script de limpeza e manutenção de sistema. O objetivo é criar uma ferramenta robusta que identifique arquivos temporários, logs antigos e outros itens desnecessários, remova-os com segurança e mantenha um registro detalhado de todas as ações. Esse tipo de script é essencial para administradores de sistemas e usuários avançados que desejam automatizar tarefas de manutenção, economizando tempo e evitando erros manuais.
Vamos construir o script passo a passo, explorando cada fase: identificação de arquivos, remoção segura, implementação de um modo de simulação (dry-run) e registro de logs. Ao final, você terá um script funcional que pode ser adaptado e expandido para suas necessidades específicas, sempre com foco em segurança e boas práticas.
Identificando arquivos
O primeiro passo em qualquer script de limpeza é identificar quais arquivos devem ser removidos. No PowerShell, podemos usar cmdlets como Get-ChildItem para listar arquivos e Where-Object para filtrar por critérios como idade, tamanho ou extensão. Vamos focar em arquivos temporários e logs antigos, que são comuns em sistemas Windows.
Para encontrar arquivos temporários, podemos pesquisar em diretórios como $env:TEMP (que aponta para a pasta Temp do usuário) e C:\Windows\Temp. Também podemos incluir logs de aplicativos que ficam em pastas como C:\Logs ou C:\ProgramData.
Um script eficiente deve parametrizar os caminhos e critérios, permitindo que o usuário especifique quais diretórios limpar e por quanto tempo reter arquivos. Vamos criar uma função que retorna uma lista de arquivos que atendem aos critérios definidos.
# Função para identificar arquivos antigos em um diretório
function Get-OldFiles {
param(
[string]$Path,
[int]$DaysOld = 30,
[string[]]$Include = @('*.*')
)
$cutoffDate = (Get-Date).AddDays(-$DaysOld)
Get-ChildItem -Path $Path -Include $Include -File -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $cutoffDate }
}
# Exemplo de uso: encontrar arquivos temporários com mais de 7 dias
$tempFiles = Get-OldFiles -Path $env:TEMP -DaysOld 7
$tempFiles | Select-Object FullName, LastWriteTime, Length
Neste exemplo, a função Get-OldFiles aceita um caminho, uma idade mínima em dias e um filtro de inclusão. Ela usa -ErrorAction SilentlyContinue para ignorar diretórios sem permissão de acesso, evitando que o script falhe. O parâmetro -Recurse garante que subdiretórios também sejam varridos.
É importante considerar que a busca recursiva pode ser lenta em grandes diretórios. Para otimizar, podemos limitar a profundidade ou usar filtros mais específicos. Além disso, devemos verificar se o diretório existe antes de tentar acessá-lo, usando Test-Path. Vamos melhorar nossa função incluindo essa verificação.
function Get-OldFiles {
param(
[string]$Path,
[int]$DaysOld = 30,
[string[]]$Include = @('*.*')
)
if (-not (Test-Path -Path $Path -PathType Container)) {
Write-Warning "Diretório não encontrado: $Path"
return
}
$cutoffDate = (Get-Date).AddDays(-$DaysOld)
Get-ChildItem -Path $Path -Include $Include -File -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $cutoffDate }
}
Agora, para reunir arquivos de vários diretórios, podemos combinar os resultados. Vamos criar um array de caminhos e processá-los em loop, acumulando os arquivos encontrados.
$targets = @(
$env:TEMP,
'C:\Windows\Temp',
'C:\Logs'
)
$allOldFiles = @()
foreach ($target in $targets) {
$files = Get-OldFiles -Path $target -DaysOld 7
if ($files) {
$allOldFiles += $files
}
}
Write-Host "Encontrados $($allOldFiles.Count) arquivos antigos."
Com essa abordagem, temos uma lista consolidada de arquivos que podem ser removidos. No entanto, devemos ter cuidado para não incluir arquivos que ainda estão em uso ou que são importantes. Por isso, a próxima seção abordará a remoção segura.
Remoção segura
Remover arquivos de forma segura é crucial para evitar perda de dados ou quebra de aplicações. A remoção segura envolve várias camadas: verificar se o arquivo não está em uso, mover para a Lixeira em vez de excluir permanentemente, e permitir uma confirmação manual ou um modo de reversão.
O PowerShell tem o cmdlet Remove-Item, mas ele exclui permanentemente. Para enviar para a Lixeira, podemos usar o namespace Microsoft.VisualBasic.FileIO.FileSystem ou a API do Shell. Vamos criar uma função que usa a Lixeira para maior segurança.
# Função para mover arquivo para a Lixeira
function Move-ToRecycleBin {
param([string]$Path)
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($Path, 'OnlyErrorDialogs', 'SendToRecycleBin')
}
Além disso, devemos tentar identificar se o arquivo está em uso. Uma técnica é tentar abrir o arquivo com exclusividade. Se falhar, provavelmente está em uso. Vamos implementar uma função de teste de bloqueio.
function Test-FileLock {
param([string]$Path)
try {
$stream = [System.IO.File]::Open($Path, 'Open', 'ReadWrite', 'None')
$stream.Close()
return $false
} catch {
return $true
}
}
Combinando essas funções, podemos criar um processo de remoção que primeiro verifica se o arquivo está bloqueado; se não estiver, move para a Lixeira; se estiver, registra um aviso e pula.
foreach ($file in $allOldFiles) {
if (Test-FileLock -Path $file.FullName) {
Write-Warning "Arquivo em uso, ignorando: $($file.FullName)"
continue
}
try {
Move-ToRecycleBin -Path $file.FullName
Write-Host "Removido: $($file.FullName)" -ForegroundColor Green
} catch {
Write-Error "Falha ao remover $($file.FullName): $_"
}
}
Essa abordagem garante que arquivos em uso não sejam removidos e que os removidos possam ser recuperados da Lixeira, se necessário. É uma prática recomendada para scripts de manutenção, pois oferece uma rede de segurança.
Outra camada de segurança é permitir que o usuário especifique uma lista de exclusões (padrões de arquivo ou pastas) que nunca devem ser removidos. Vamos adicionar isso ao script principal.
Dry-run
O modo dry-run, também conhecido como simulação, é uma funcionalidade essencial em scripts de manutenção. Ele permite que o script mostre o que seria feito sem realmente executar as ações. Isso é útil para testar o script, revisar os arquivos que seriam removidos e evitar surpresas.
No PowerShell, podemos implementar um parâmetro -WhatIf nos cmdlets, mas para nossa função personalizada, vamos criar uma flag -DryRun que, quando presente, apenas exibe as ações que seriam tomadas.
Vamos modificar nosso loop de remoção para verificar essa flag. Se estiver ativa, em vez de chamar Move-ToRecycleBin, apenas exibimos uma mensagem indicando que o arquivo seria removido.
param(
[switch]$DryRun
)
# ... código anterior ...
foreach ($file in $allOldFiles) {
if (Test-FileLock -Path $file.FullName) {
Write-Warning "Arquivo em uso, ignorando: $($file.FullName)"
continue
}
if ($DryRun) {
Write-Host "[DRY-RUN] Removeria: $($file.FullName)" -ForegroundColor Yellow
} else {
try {
Move-ToRecycleBin -Path $file.FullName
Write-Host "Removido: $($file.FullName)" -ForegroundColor Green
} catch {
Write-Error "Falha ao remover $($file.FullName): $_"
}
}
}
Para facilitar o uso, podemos também adicionar um resumo no final do dry-run, mostrando quantos arquivos seriam removidos e o espaço total que seria liberado. Isso ajuda o usuário a decidir se deve executar a limpeza de verdade.
if ($DryRun) {
$totalSize = ($allOldFiles | Measure-Object -Property Length -Sum).Sum
Write-Host "\n[DRY-RUN] $($allOldFiles.Count) arquivos seriam removidos, liberando $([math]::Round($totalSize / 1MB, 2)) MB."
}
O dry-run é uma prática recomendada em qualquer script que modifica o sistema. Ele permite que administradores revisem as ações antes de aplicá-las, reduzindo o risco de erros. Em ambientes corporativos, é comum exigir que scripts de manutenção sejam executados em modo simulação antes de uma execução real.
Logging
Manter um log das ações executadas é fundamental para auditoria e solução de problemas. Um bom log deve registrar a data e hora, o arquivo processado, a ação (remoção, erro, ignorado) e qualquer informação relevante.
No PowerShell, podemos criar um log em formato texto ou CSV. Vamos implementar uma função Write-Log que adiciona entradas a um arquivo de log. O log pode ser armazenado em uma pasta padrão, como C:\Logs\CleanupLog.csv.
# Função para registrar eventos no log
function Write-Log {
param(
[string]$Action,
[string]$File,
[string]$Message = ''
)
$logEntry = [PSCustomObject]@{
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Action = $Action
File = $File
Message = $Message
}
$logEntry | Export-Csv -Path $logPath -Append -NoTypeInformation
}
# Definir caminho do log
$logPath = 'C:\Logs\CleanupLog.csv'
No loop de remoção, chamamos Write-Log para cada evento. Por exemplo, quando um arquivo é removido, registramos 'Removido'; quando está em uso, 'Ignorado'; quando há erro, 'Erro'. No dry-run, podemos registrar 'Simulado' ou simplesmente não registrar, dependendo da preferência.
foreach ($file in $allOldFiles) {
if (Test-FileLock -Path $file.FullName) {
Write-Log -Action 'Ignorado' -File $file.FullName -Message 'Arquivo em uso'
Write-Warning "Arquivo em uso, ignorando: $($file.FullName)"
continue
}
if ($DryRun) {
Write-Host "[DRY-RUN] Removeria: $($file.FullName)" -ForegroundColor Yellow
Write-Log -Action 'Simulado' -File $file.FullName
} else {
try {
Move-ToRecycleBin -Path $file.FullName
Write-Host "Removido: $($file.FullName)" -ForegroundColor Green
Write-Log -Action 'Removido' -File $file.FullName
} catch {
Write-Error "Falha ao remover $($file.FullName): $_"
Write-Log -Action 'Erro' -File $file.FullName -Message $_
}
}
}
O log em CSV pode ser aberto no Excel ou em outras ferramentas para análise. Além disso, podemos criar um log resumido no final, mostrando estatísticas da execução.
$summary = @{
Total = $allOldFiles.Count
Removidos = ($allOldFiles | Where-Object { $_.Removed }).Count
Ignorados = ($allOldFiles | Where-Object { $_.Ignored }).Count
}
Write-Log -Action 'Resumo' -File '' -Message "Total: $($summary.Total), Removidos: $($summary.Removidos), Ignorados: $($summary.Ignorados)"
Para tornar o script mais robusto, podemos adicionar a criação automática da pasta de log se ela não existir, usando New-Item -ItemType Directory.
if (-not (Test-Path (Split-Path $logPath -Parent))) {
New-Item -ItemType Directory -Path (Split-Path $logPath -Parent) -Force
}
O logging é vital para entender o que o script fez, especialmente se ele for executado em segundo plano ou agendado. Com um log detalhado, você pode verificar se a limpeza foi bem-sucedida e investigar problemas.
Agora, vamos juntar todas as peças em um script completo e organizado. O script final terá parâmetros para caminhos, idade, dry-run, e opção de log. Vamos também incluir uma função Test-Path para validar os diretórios antes de prosseguir.
# Script de limpeza e manutenção
param(
[string[]]$TargetPaths = @($env:TEMP, 'C:\Windows\Temp', 'C:\Logs'),
[int]$DaysOld = 30,
[switch]$DryRun,
[string]$LogPath = 'C:\Logs\CleanupLog.csv'
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
# Funções auxiliares
function Get-OldFiles { ... } # definida anteriormente
function Test-FileLock { ... }
function Move-ToRecycleBin { ... }
function Write-Log { ... }
# Garantir pasta de log
$logDir = Split-Path $LogPath -Parent
if ($logDir -and -not (Test-Path $logDir)) {
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
}
# Coletar arquivos antigos
$allOldFiles = @()
foreach ($target in $TargetPaths) {
$files = Get-OldFiles -Path $target -DaysOld $DaysOld
if ($files) {
$allOldFiles += $files
}
}
Write-Host "Encontrados $($allOldFiles.Count) arquivos antigos." -ForegroundColor Cyan
# Processar arquivos
foreach ($file in $allOldFiles) {
if (Test-FileLock -Path $file.FullName) {
Write-Log -Action 'Ignorado' -File $file.FullName -Message 'Arquivo em uso'
Write-Warning "Arquivo em uso, ignorando: $($file.FullName)"
continue
}
if ($DryRun) {
Write-Host "[DRY-RUN] Removeria: $($file.FullName)" -ForegroundColor Yellow
Write-Log -Action 'Simulado' -File $file.FullName
} else {
try {
Move-ToRecycleBin -Path $file.FullName
Write-Host "Removido: $($file.FullName)" -ForegroundColor Green
Write-Log -Action 'Removido' -File $file.FullName
} catch {
Write-Error "Falha ao remover $($file.FullName): $_"
Write-Log -Action 'Erro' -File $file.FullName -Message $_
}
}
}
# Resumo
if ($DryRun) {
$totalSize = ($allOldFiles | Measure-Object -Property Length -Sum).Sum
Write-Host "\n[DRY-RUN] $($allOldFiles.Count) arquivos seriam removidos, liberando $([math]::Round($totalSize / 1MB, 2)) MB." -ForegroundColor Magenta
} else {
Write-Host "\nLimpeza concluída. Verifique o log em $LogPath" -ForegroundColor Magenta
}
Este script é um ponto de partida sólido. Você pode expandi-lo adicionando mais critérios, como tamanho mínimo, ou integrando com o Agendador de Tarefas para execução automática.
Boas práticas e observações finais
Ao desenvolver scripts de manutenção, sempre priorize a segurança. Use a Lixeira em vez de exclusão permanente, implemente dry-run por padrão e mantenha logs detalhados. Teste em um ambiente controlado antes de executar em produção.
Além disso, considere usar o módulo PSWriteHTML para gerar relatórios visuais do log, ou integrar com o PSScriptAnalyzer para verificar a qualidade do código. Lembre-se de documentar seu script com comentários e incluir ajuda (Get-Help).
Referências
- Get-ChildItem - Documentação oficial Microsoft
- Remove-Item - Documentação oficial Microsoft
- Write-Host - Documentação oficial Microsoft
- Export-Csv - Documentação oficial Microsoft
- Trabalhar com arquivos e pastas - Documentação oficial Microsoft
- Exemplos de scripts para administração - Microsoft
- Método DeleteFile - API Shell (Lixeira)
Exercícios
Explique por que é importante usar a Lixeira em vez de exclusão permanente em scripts de limpeza.
✓ Resposta: Usar a Lixeira permite recuperação acidental de arquivos, reduzindo o risco de perda de dados. Exclusão permanente é irreversível, e em scripts automáticos é essencial ter uma rede de segurança.Escreva uma função PowerShell que verifique se um arquivo está em uso (bloqueado).
✓ Resposta:function Test-FileLock { param([string]$Path) try { $stream = [System.IO.File]::Open($Path, 'Open', 'ReadWrite', 'None') $stream.Close() return $false } catch { return $true } }Como você implementaria um modo dry-run em um script de limpeza? Dê um exemplo de código.
✓ Resposta: Implementamos um parâmetro switch -DryRun. No loop, se -DryRun estiver presente, apenas exibimos as ações sem executá-las. Exemplo:if ($DryRun) { Write-Host "[DRY-RUN] Removeria: $($file.FullName)" } else { Remove-Item $file.FullName -Force }Qual a finalidade de registrar logs em um script de manutenção? Liste pelo menos três informações que devem constar no log.
✓ Resposta: O log permite auditoria, depuração e acompanhamento das ações. Informações essenciais: timestamp, caminho do arquivo, ação realizada (removido, ignorado, erro), e mensagens de erro/aviso.No script final apresentado, qual é o propósito do parâmetro -TargetPaths? Como ele melhora a flexibilidade do script?
✓ Resposta: O parâmetro -TargetPaths permite que o usuário especifique quais diretórios serão varridos, em vez de fixar caminhos. Isso torna o script reutilizável para diferentes cenários, sem precisar editar o código.