PowerShell Cleanup Tips

PowerShell scripts can use and create many types out output. Some output is needed for archival purposes, picked up by other scripts, emailed to recipients and stored as queries for latest references. If not maintained properly, the output from your PowerShell scripts could lead to a lot of clutter. What kinds of clutter are we referring to?

Types of output:

  • Files examples – CSV, txt, html
  • Queries – Compliance Search in SCC

Now the above list is not a comprehensive list of all types of output that could clutter your environment, but it contains some of the more common output from PowerShell.

How To Clean-Up

The hardest part about cleanup is keeping track of what was created, what is needed and what should be removed. For this tip we’ll cover some possible things that can be done to handle some PowerShell generated clutter.

Example 1

For this example a request was made to create a series of Compliance Searches (In the Security and Compliance Center) that are being used to remove emails from mailboxes. Each mailbox will have a unique Compliance Search created for them. Now these searches will be activated and put into action via PowerShell. The issue is that these cases can clutter up PowerShell and your tenant is not cleaned up. If, for example, a 1000 of these were run, you may need to sort these out by PowerShell to find a new one or an old one that was not part of this 1000 set.

Let’s walk through the creation, tracking and removal process:

For this code section, a list of mailboxes contained in a CSV file are stored in the $Mailboxes variable. The $Mailboxes variable will be put through a loop to use each line to create the Compliance Searches:
[sourcecode language=”powershell”]
$SearchList = @()
$SearchList += 'Name'
$Mailboxes = Import-CSV C:\scripts\source\mailboxes.csv
Foreach ($Mailbox in $Mailboxes) {
$Search = "$Mailbox-Search"
$SearchList += $Search
New-ComplianceSearch -Name $Search -ExchangeLocation $Mailbox -ContentMatchQuery "from:$Sender"
Start-ComplianceSearch -Identity $Search
New-ComplianceSearchAction -SearchName $Search -Purge -PurgeType SoftDelete -Confirm:$False
}
[/sourcecode]
In the above scripts $SearchList is used to store a list of searches by name. We can use this list to delete all of these automatically or manually. In order to do that, we can run this code section:
[sourcecode language=”powershell”]
# Cleanup searches or leave behind?
$Counter = 0
If ($Answer -eq 'M') {
Write-host "To delete the searches, simply answer 'y' or 'n' to confirm or not confirm it's deletion:" -ForegroundColor Yellow
Write-host "—————————————————————————————–" -ForegroundColor Yellow
ForEach ($Line in $SearchList) {
If ($Counter -gt 0) {
Write-host "The Search $Line needs to be removed?"
Remove-ComplianceSearch -Identity $Line -Confirm:$True
}
$Counter++
}
} Else {
Write-host "Deleting all Compliance Searches in the list" -ForegroundColor Yellow
ForEach ($Line in $SearchList) {
If ($Counter -gt 0) {
Write-host "The Search $Line needs to be removed."
Write-host "Removing $Line Compliance Search" -ForegroundColor White
Remove-ComplianceSearch -Identity $Line -Confirm:$False
}
$Counter++
}
}
[/sourcecode]
Now we have a script that can create all the compliance searches we need and then allow us to clean them out at the end of the script in order to keep the clutter for Compliance Searches clear.

Example 2
For this next example we need to perform a similar task for cleanup.

In order to remove all of these CSV files, we’ll need to keep track of the CSV files that were used. In a sample scenario CSV files are being used for output of findings that take place during the script’s processing. Over time these output files get hard to manage as some need to be deleted and others do not. How can we keep track of the files? Same way we did above. The key is locating the output files in a known directory and with a good naming convention as to help with the deletion process.
[sourcecode language=”powershell”]
# Define locsation for files
$Path = (Get-Item -Path ".\" -Verbose).FullName
# Set up scratch file variable for files to delete at the end of the script
$ScratchFiles = @()
$ScratchFiles += 'Name'
# Loop 1
$Line = 'Good archival output' | Outfile $Path+"\ArchivalInfo.csv"
$Line1 = 'Temp output One' | Outfile $Path+"\TempInfoOne.csv"
$Line2 = 'Tmpt output two' | Outfile $Path+"\TempInfoTwo.csv"
$ScratchFiles += $Path+"\TempInfoOne.csv"
$ScratchFiles += $Path+"\TempInfoTwo.csv"
# Loop 2
$Line = 'Good current output' | Outfile $Path+"\ArchivalInfo.csv"
$Line1 = 'Current One' | Outfile $Path+"\TempCurrentOne.csv"
$Line2 = 'Current Two' | Outfile $Path+"\TempCurrentTwo.csv"
$ScratchFiles += $Path+"\TempCurrentOne.csv"
$ScratchFiles += $Path+"\TempCurrentTwo.csv"
# Cleanup at end of the script
Foreach ($File in $ScratchFiles) {
$FileName = $File.Name
Remove-Item -Path $FileName -Force -Confirm:$False
}
[/sourcecode]
As you can see from the script, we can remove all files simply by storing their name and path. Then the is of Remove-Item works as expected.

Conclusion

Why bother doing this? Simply put – removing clutter. If you use automated scripts that are rarely monitored, there maybe hundreds or thousands of resulting files that are left behind. Cleaning these up post change could also be a pain if you are unaware of which files relate to what process. So keep it clean, do it with the script that creates them. If a file is needed for reference later on, create an Archive older and appropriately name files and put them in that folder.

Related Post

A Good EndingA Good Ending

Like all good scripts, starting off well should be reciprocated with a good ending as well. What does that mean? Think processing, cleanup, ending transcripts, truncating log files and more.