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
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
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"}
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
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.