Quando o PowerShell script usando Foreach e Search-Mailbox apagam mais do que o esperado

 

Por: Eduardo Tavares de Almeida

 

Trabalhar com script em PowerShell as vezes pode causar erros inesperados. Por isso sempre recomendamos testar em um ambiente de laboratório onde seja o mais fiel possível ao de produção.

 

O PowerShell também dá opções de simular o que será feito como -WhatIf mostrando o que seria alterado, sem alterar nada.

 

Mas vamos ao exemplo que aconteceu. O administrador queria rodar um comando no PowerShell do Exchange 2010 para apagar tudo das Rooms Mailboxes que foram recebidos 1 ano atrás.

 

Então o script parecido como o exemplo abaixo foi criado, mas o resultado não foi o esperado. Um dos motivos foi que nenhuma Mailbox com aquele prefixo existia e no lugar ele foi executado para todas causando um problemão. Então ficamos com a pergunta, se a mailbox não existia, porque foi executado para todas e não para nenhuma?!?

 

Este foi o script:

 

$date = ((get-date).AddDays(-365)).ToString("M/d/yyyy")

$Rooms = Get-MailBox -Identity "Room Mailbox 5 *” -ResultSize unlimited

Foreach ($Name in $Rooms)

{

Get-Mailbox -Identity $Name -Resultsize Unlimited | Search-Mailbox -SearchQuery "Received:< $date" -DeleteContent -Force

}

 

Vamos entender cada parte do script para poder entender o porquê. Essa é a primeira linha:

 

$date = ((get-date).AddDays(-365)).ToString("M/d/yyyy")

 

Nessa parte ele pega a data atual e diminui 365 dias atribuindo a varável $date. Rodando o comando $date confirmamos o valor

 

image

 

 

 

Essa linha é onde começa a confusão

 

$Rooms = Get-MailBox -Identity "Room Mailbox 5 *” -ResultSize unlimited

 

O esperado nesse comando seria deixar a todas as mailboxes que começam com esse nome na collection $Rooms, mas no caso dele, essa mailbox não existia. Dessa forma, ao rodar o comando get-mailbox, recebemos o erro que ela não foi encontrada:

 

[PS] C:\>$Rooms = Get-MailBox -Identity "Room Mailbox 5*" -ResultSize unlimited

The operation couldn't be performed because object 'Room Mailbox 5*' couldn't be found on 'DC01.virtual.local'.

    + CategoryInfo : NotSpecified: (:) [Get-Mailbox], ManagementObjectNotFoundException

    + FullyQualifiedErrorId : 6D0CE178,Microsoft.Exchange.Management.RecipientTasks.GetMailbox

 

Mas então qual o valor da collection $Rooms?

Se rodarmos o comando $Rooms ele não retorna nada, isso o torna Nulo como demonstramos a seguir

 

image

              

 

 

Ok, então temos uma collection nula até o momento e novamente a pergunta, porque o comando iria rodar na próxima linha entrando no Foreach e executando o Search-Mailbox?

 

Foreach ($Name in $Rooms)

{

…….

}

 

Vamos a parte mais interessante e ver como Foreach funciona.

 

Se executarmos o comando abaixo, considerando que nunca declaramos a collection $Roomwwwwwws, vamos ver que ele irá rodar uma vez pelo menos. Esse comportamento ocorre no Exchange 2010 que usa o PowerShell 2.0

 

Foreach ($Name in $Roomwwwwwws) { Echo "Run once"}

 

image

 

 

 

 

 

 

No PowerShell 2.0, esse comportamento é padrão mas merece uma explicação. No PowerShell, o valor null é valor escalar.

Uma collection pode conter vários valores nulls, o que faria o foreach executar várias vezes

 

foreach ($i in $null,$null,$null) { } # - Executa 3 vezes

foreach ($i in $null, $null) {} # - Executa 2 vezes

foreach ($i in $null) {} # - Executa 1 vez

 

No Exchange 2013 que usa a verão 4 do PowerShell esse comportamento foi alterado e não mais será executado para valores null

 

image

 

 

 

 

Isso explica então porque o foreach foi executado a primeira vez.

 

Vamos então a linha que foi executado 1 vez dentro do foreach

 

Get-Mailbox -Identity $Name -Resultsize Unlimited | Search-Mailbox -SearchQuery "Received:< $date" -DeleteContent -Force

 

 

Essa primeira parte é a que mais interessa Get-Mailbox -Identity $Name. Como a collection está com valor nula, em vez de retornar erro dizendo que ele não encontrou a mailbox, o que o PowerShell faz é desconsiderar o parâmetro Identity, então nesse caso seria o mesmo que executar Get-Mailbox. Como o retorno desse comando irá buscar todas mailboxes, o resultado para p próximo comando será a deleção em todas mailboxes causando então o transtorno.

 

O comando Search-Mailbox também tem o parâmetro -LogOnly que irá ajudar a verificar todas mailboxes que seriam afetas antes de qualquer alteração.