Saturday, June 21, 2008

Using PowerShell to Retrieve PSTs

Practically every company is going to be involved in a law suit at some point or another. As an IT professional, it's usually our job to help comply with an order to produce copies of email when our company gets sued. In a perfect world, all your employee's email resides in one place and can easily be exported and produced. Back on Earth, employees store their email in a multitude of locations (Exchange servers, local PST files, network PST files, blackberries, etc.) forcing you to exhaustively search all of them for email. Such was the case with my company last month when we received a list of forty or so employees who's email had to be produced. Archiving email from an exchange mailbox is a pretty easy task and doesn't require the employee's cooperation. Archiving PSTs stored on laptops, desktops, and network storage locations is another matter all together. To make the process easier, I decided to write a PowerShell script to automate as much of the process as possible.

Since all I had to start with was a list of employee names, I decided to store all my employee names and search locations in a CSV file. Each line in the CSV file would have the employee's name, their user name, their computer's admin share path, their home folder path, and finally their profile path. A sample CSV might look like this:

"Name", "UserName", "Computer", "Home", "Profile"
"John Doe", "JDoe", "\\Desktop-JDoe\c$", "\\FileServer\Home\JDoe", "FileServer\Profile\JDoe"

Once we create our CSV, we can import it into a PowerShell CSV object. The import looks like this:


Now that we have all of our users and all the network locations we need to search saved into a usable format, we can start writing our PowerShell script. The script itself simply takes in a network location, recursively scans that location for any PST files, adds them to a list, and then attempts to copy those files to a local directory named for the user. If the copy fails, if the PST is locked because that user has Outlook open, or if a search path is unavailable because a user has turned their computer off, then we want to write the skipped PSTs and unavailable search paths to a text file so we can manually track them down later. The quick and dirty version of my script looked like this:
#movePST.ps1
param( $userName, $searchPath, $errorLog )

function copyPST ( $sourcePST ) {
if(!$sourcePST.exists){ $sourcePST.fullname | Out-File $errorLog -Append; return $null }
$error.clear()
Write-Host "Copying " $sourcePST.fullname
Copy-Item -Path $sourcePST.fullname -Destination ($objDestination.fullname + "\" + $counter + $sourcePST.name) -Force
if($error -ne $null){ $sourcePST.fullname | Out-File $errorLog -Append; $error.clear(); return $null }
}

#Check inputs and look for errors
if(!$userName -or !$searchPath -or !$errorLog) {Write-Host "Missing input"; return $null}
$objDestination = get-item ("C:\PSTs\" + $userName)
if(!$objDestination.exists) {Write-Host "Destination folder has not been created: " $objDestination; return $null}
$counter = 1

#record invalid / unreachable search paths
$objSearchPath = Get-Item $searchPath -ErrorAction SilentlyContinue
if($objSearchPath -eq $null){ $SearchPath.toString() | Out-File $errorLog -Append; return $null }

#Look for PSTs and store them in an array
Write-Host "Searching" $userName
$arrayPSTs = Get-ChildItem $objSearchPath -Recurse -Force -Include *.pst -ErrorAction SilentlyContinue
foreach($objPST in $arrayPSTs) {
copyPST($objPST)
$counter += 1
}

The first function actually copies the PST once it's identified. In some situations, the user might have multiple PSTs on their computer with the same name. To solve this, we create a counter, add that number to the beginning of each PST's file name, and increment the counter with each PST we found. If the PST copy fails, the name of the PST is appended to our error log.

The main block of code first does some basic testing of inputs to make sure everything is correctly specified. Once that's taken care of, the script checks the search path. If the path is unavailable (maybe because the user turned off their computer when they weren't supposed t0), then we append that search path to our error log and move on. The last little block of code recursively searches through the search path and sends every PST file to the copyPST funtion to be archived.

Now that we have our script and our CSV containing all our locations, it's time to actually perform the search. Since the script cannot copy PST files that are open inside Outlook, we had to make sure that everyone went home with their computers turned on and Outlook closed. Then we're all ready to start the scan. This example code would search through every entry in our CSV and search each person's computer for PSTs:

foreach ($user in $csv) {./movePSTs.ps1 -userName $user.Name -searchPath $user.Computer -errorLog ./error.txt}

To search the profile and home paths, you would run the same code but substitute $user.Home and $user.Profile for the $user.Computer parameter. If all goes well, you wind up with a folder containing all the CSVs organized by name. Any errors show up in the error.txt file and can be archived later.

One final note: Depending on the industry you're in, your company may have to frequently produce emails. If this might be a possibility, make sure you take that into account when you come up with IT policies regarding email and PSTs. You should also spend some more time figuring out the best ways to archive email and which of those steps you can automate with PowerShell. The last piece of the puzzle is training your users on what to expect when you perform an email archive. Their cooperation makes the process much easier. Happy litigating!

No comments: