Início > Windows PowerShell > PoSh – Criando uma tarefa de remoção baseada em um arquivo CSV

PoSh – Criando uma tarefa de remoção baseada em um arquivo CSV

janeiro 3, 2016

Olá pessoALL,

Primeiramente, espero que tenham tido um feliz natal e que este ano de 2016 seja melhor que 2015 para todos nós.

Introdução

Hoje eu vou falar sobre um processo que é comum no nosso dia a dia: o manuseio de arquivos que precisam ser removidos após um período de tempo específico.

Para este tópico, vamos pensar em uma aplicação que faz uso de arquivos de log para armazenadas informações sobre seu ciclo de processamento. Vamos imaginar também que esta aplicação faça a criação diária destes arquivos e que o desenvolvedor foi negligente não criando uma tarefa interna para manusear estes arquivos.

Como podemos presumir, uma aplicação gerando arquivos de log diariamente irá causar cedo ou tarde um problema de exaustão de espaço no volume onde estes logs são armazenados e por fim, em um ambiente onde existe um monitoramento ativo, um alerta para o time de infraestrutura que deverá executar alguma ação.

Em um ambiente corretamente implementado, você não encontraria arquivos de log sendo depositados no mesmo volume onde estão localizados os binários de instalação de uma ou mais aplicação por segurança. Portanto, é esperado que tenhamos um volume dedicado para arquivos de log.

Em uma situação como esta onde há apenas uma aplicação, usando uma simples linha de comando em PowerShell seria suficiente para criarmos uma tarefa agendada e removermos o conteúdo que desejamos.

Get-ChildItem -Path L:\Logs -Recurse | Remove-Item -Force

Usando o comando acima é possível remover todo o conteúdo do diretório L:\Logs sem nenhum problema, porém, não estamos preservando um determinado período para consulta – digamos sete dias. Não fique preocupado! Criarmos esta condição é extremamente simples como podemos ver abaixo:

Get-ChildItem -Path L:\Logs -Recurse | ? {$_.CreationTime -lt (Get-Date).AddDays(-7)} | Remove-Item -Force

Pronto! Acabamos de resolver o problema do período de retenção de arquivos. Já acabamos? Você acha que já acabamos? Ainda não…

O cenário

O comando acima pode resolver uma situação simples onde há somente um tipo de arquivo a ser tratado, mas, e se houver diferentes tipos de arquivos em diretórios distintos?

Digamos que existem diferentes tipos de períodos de retenção. Como resolver este tipo de situação? E se houver arquivos no diretório X com extensão A que precisam ser removidos, mas que não podem sofrer o mesmo tipo de tratamento para o diretório Y?

Pois bem… Há diversas formas de resolvermos este tipo de situação, algumas delas robustas e outras não. Uma forma nada robusta de resolvermos este tipo de situação seria criarmos um conjunto de comandos que fariam sequencialmente a remoção baseada em critérios específicos, mas na minha opinião, é um péssimo hábito tal solução.

Além de ser horrível visualmente, este tipo de solução mostra despreparo e falta de conhecimento sobre lógica de programação e otimização de código. Uma forma otimizada de realizarmos esta tarefa seria utilizando um array contendo as informações necessárias e um laço (FOREACH).

Podemos criar objetos para cada item que precisamos e então consolidarmos estes objetos em um array. Criar objetos em PowerShell é extremante simples! Vamos ver abaixo como podemos criar um array para dois diretórios como mostrado abaixo:

  • Diretório L:\Logs\App1, extensões existentes .log e .txt com retenção de 7 dias.
  • Diretório L:\Logs\App2, extensões existentes .log e .txt com retenção de 1 dia, mas os arquivos .txt não podem ser removidos.

Pensando e construindo uma solução

Com base nas informações acima, abra o PowerShell ISE e cole o conteúdo abaixo:

$Configs = @()
$Object1 = New-Object System.Object
$Object1 | Add-Member -MemberType NoteProperty -Name FolderPath -Value “L:\Logs\App1”
$Object1 | Add-Member -MemberType NoteProperty -Name Extensions -Value “.log;.txt”
$Object1 | Add-Member -MemberType NoteProperty -Name RetainDays -Value “7”
$Configs += $Object1
$Object1 = New-Object System.Object
$Object1 | Add-Member -MemberType NoteProperty -Name FolderPath -Value “L:\Logs\App2”
$Object1 | Add-Member -MemberType NoteProperty -Name Extensions -Value “.log”
$Object1 | Add-Member -MemberType NoteProperty -Name RetainDays -Value “1”
$Configs += $Objec1

Agora, vamos entender o que cada linha está fazendo caso você não tenha tanta intimidade com PowerShell…

$Configs = @() é uma forma simples de declararmos em PowerShell que uma variável será do tipo Array. Outra forma de fazermos o mesmo é usando uma classe .NET $Configs = New-Object System.Collections.ArrayList. Como pretendemos usar um array para armazenar estas informações, criamos um chamado $Configs.

Agora que temos nossa variável do tipo Array, vamos construir os objetos que formarão nosso array de configurações. Para fazer isso, precisamos criar um objeto em branco para inserirmos as colunas e informações que precisamos. Para criarmos nosso objeto usaremos a declaração $Object1 = New-Object System.Object.

Já temos a variável do tipo array que irá armazenar nossas informações e também nosso objeto em branco para trabalharmos e inserirmos estas informações. O próximo passo é criarmos propriedades (colunas) e inserirmos os valores necessários.

A inclusão de uma propriedade em um objeto em branco é feita de uma forma diferente do normal. Em um primeiro momento, você deve imaginar que para incluirmos alguma coisa devemos utilizar o sinal de igual (=), mas com o uso do CmdLet Add-Member o correto é utilizarmos o pipe (|) para esta inclusão.

Então para criarmos o que precisamos, nós vamos criar três propriedades: FolderPath, Extensions e RetainDays.

$Object1 | Add-Member -MemberType NoteProperty -Name FolderPath -Value “L:\Logs\App1”
$Object1 | Add-Member -MemberType NoteProperty -Name Extensions -Value “.log;.txt”
$Object1 | Add-Member -MemberType NoteProperty -Name RetainDays -Value “7”

O CmdLet Add-Member foi usado para inserirmos uma propriedade com nome FolderPath contendo o valor L:\Logs\App1 do tipo NoteProperty. O tipo NoteProperty indica que estamos inserindo um propriedade estática, ou seja, estamos definido um valor fixo para a propriedade de nome FolderPath.

Desta forma nós criamos as propriedades necessárias para incluirmos a primeira linha de nosso array de informações.

Agora que já temos nosso objeto preenchido, vamos inserir as informações em nosso array usando o comando $Configs += $Object1 (mais igual (+=) é a forma de informarmos ao PowerShell que nós estamos acrescentando dados).

Em seguida nós vamos utilizar a nossa variável chamado $Object1 para inserirmos nossa segunda linha de informações no array, mas antes de fazermos isto, é preciso zerar a variável. Para executarmos esta limpeza iremos utilizar o mesmo comando para criarmos um objeto em branco novamente $Object1 = New-Object System.Object.

Feito isto, vamos preencher as propriedades com as informações do segundo item de configuração e por fim acrescentar novamente as informações deste novo objeto ao array.

$Object1 | Add-Member -MemberType NoteProperty -Name FolderPath -Value “L:\Logs\App2”
$Object1 | Add-Member -MemberType NoteProperty -Name Extensions -Value “.log”
$Object1 | Add-Member -MemberType NoteProperty -Name RetainDays -Value “1”
$Configs += $Objec1

Pronto! Já temos nosso array com as informações que desejamos e para conferirmos seu conteúdo, basta digitar $Configs e pressionar ENTER.

PoSh-RemoveLogTask-0001

Como podemos visualizar na imagem anteriormente o resultado é o que esperávamos: um array com as informações que precisamos.

Embora seja possível a abordagem acima, em uma situação onde vários diretórios existem, criar vários objetos iria poluir o código na minha opinião. Então, como podemos chegar ao mesmo resultado acima? Uma forma de fazermos isto é usar um arquivo CSV.

Por padrão, quando importamos um arquivo CSV separado por vírgulas usando o CmdLet Import-CSV, o resultado que temos é o mesmo apresentado anteriormente na variável $Configs. Deixe me mostrar como isto pode ser feito. A primeira coisa que precisamos é criar um arquivo CSV com o seguinte conteúdo:

FolderPath,Extensions,RetainDays
L:\Logs\App1,.log;.txt,7
L:\Logs\App2,.log,1

Salve o arquivo com o nome Configs.csv em um local de sua preferência e execute o comando Import-CSV Configs.csv para visualizar a saída. Após executa-lo, perceba que o resultado obtido é o mesmo.

PoSh-RemoveLogTask-0002

Como podemos ver, foram poupadas algumas linhas de código ao utilizarmos o CmdLet Import-CSV em conjunto com um arquivo CSV e ao mesmo tempo nós criamos um recurso externo que pode ser editado sem risco de comprometer o código do script devido a um erro operacional.

Com isto já temos o que precisamos para informar ao nosso script o que ele precisa fazer no momento de executar a remoção de arquivos, agora, vamos criar a lógica do script para executarmos esta ação de acordo com as informações do arquivo Config.csv.

Como nós estamos lidando com um array, precisamos usar um laço para percorrer seus itens e executarmos as ações necessárias. Portanto, o que nós precisamos fazer para chegarmos a este resultado é o que vemos abaixo em alguns exemplos:

Import-Csv C:\Utils\Configs.csv | ForEach {$_}
Import-Csv C:\Utils\Configs.csv | ForEach {$_.FolderPath}
Import-Csv C:\Utils\Configs.csv | ForEach {$_.Extensions}
Import-Csv C:\Utils\Configs.csv | ForEach {$_.RetainDays}

Muito simples, não é mesmo? Vamos agora começar a lapidar nosso script de remoção de arquivos.

ForEach ($Config in (Import-Csv C:\Utils\Configs.csv)) {
$Config
}

Como podemos ver, foi alterada a sintaxe do laço intencionalmente e o motivo você irá perceber em breve. Olhe novamente o conteúdo do arquivo Configs.csv e veja que na coluna Extensions há dois tipos de arquivos para o diretório L:\Logs\App1 e estes estão separados por ponto e vírgula (;).

Este sinal foi usado por uma questão pessoal, pois você pode utilizar qualquer outro sinal desde que ele não seja vírgula e o motivo é simples.

O CmdLet Import-CSV usa por padrão a vírgula para criar o Hash Table que vimos anteriormente e inserir uma vírgula a mais no seu conteúdo, causaria uma inconsistência uma vez que o número de informações seria divergente do número de colunas da tabela. O resultado seria um valor incorreto para algumas colunas e valor perdidos.

Agora que temos esta informação, vamos entender o motivo do ponto e vírgula separando as extensões que temos para cada diretório. O motivo do ponto e vírgula ou qualquer outro caractere que você escolha é criar uma referência para dividirmos os valores da coluna Extensions e tratarmos estas extensões separadamente.

Vamos ver o código abaixo para entendermos o que será feito.

ForEach ($Config in (Import-Csv C:\Utils\Configs.csv)) {
ForEach ($Extension in $Config.Extensions.Split(“;”)) {
Write-Host $Config.FolderPath $Extension $Config.RetainDays
}
}

Para separarmos os valores na coluna Extensions nós iremos utilizar o método Split usando como referência o ponto e vírgula. Desta forma, será possível trabalharmos com as extensões individualmente e por diretório sem que uma configuração interfira em outra, iremos reaproveitar o código e reduzir o número de linhas no script.

Resumindo, para cada linha encontrada no arquivo Configs.csv será feito um novo laço para separar as extensões caso existam mais de uma e então será executada uma ação com base nas configurações atuais – listar todos os arquivos com data inferior a disponível na coluna DaysToRetain que tenham a extensão da coluna Extensions.

Solução Final

Vamos ver como ficará o resultado final abaixo.

ForEach ($Config in (Import-Csv C:\Utils\Configs.csv)) {
ForEach ($Extension in $Config.Extensions.Split(“;”)) {
Get-ChildItem -Path $Config.FolderPath -Recurse | `
? {$_.LastWriteTime -lt (Get-Date).AddDays(-$Config.RetainDays) -and $_.Extension -eq $Extension} | `
Remove-Item -Force
}
}

Vejamos o que foi feito para entender a lógica usada após tratarmos as informações do arquivo CSV e fazermos a separação de acordo com o que esperamos:

  1. Usamos o CmdLet Get-ChildItem para listar todos os arquivos em todos os níveis do diretórios atribuído a variável FolderPath.
  2. Criamos uma condição para retornarmos somente o que tenha a data de criação inferior ao valor disponível em DaysToRetain atual e;
  3. Outra condição para que somente itens cuja extensão seja igual ao valor da variável $Extension atual.
  4. O resultado deste comando e condições é passado para o CmdLet Remove-Item que irá deletar os arquivos localizados como resultado.

Conclusão

Pretty easy, huh!? Como podemos ver, usando um código simples, enxuto e lógica de programação tornamos possível a criação de um script que faz a remoção de arquivos em diretórios distintos com base em informações pre definidas em um arquivo CSV.

Em resumo, você aprender um pouco sobre Arrays, Hash Tables, a criação de ambos, conceitos de operação do CmdLet Import-CSV e por fim como resolver um problema causado por arquivos que são armazenados sem nenhum critério que devem ser removidos com base em informações independentes.

Até o próximo tópico…

%d blogueiros gostam disto: