1

Trying to decipher why the copy-item is not working as expected within my ps script. I am attempting to search my fileservers for any *.pst files on all attached/valid drives, then copy those .pst files that were found to a different share location. Script below does not give me the expected results. The Out-File shows no found .pst files. When I check the destination folder I find only one *.pst file was copied over. I have validated there are several in different directories, and subfolders on the fileserver. The $Computers.txt file does not list any .pst files found or copied Screenshot
Below is my novice ps script

$Computers = hostname
get-psdrive -PSProvider "FileSystem" `
| foreach {Write-Output $Computers $_.Root;get-childitem $_.Root -include *.PST -Recurse -erroraction silentlyContinue | Copy-Item -Destination "\\Server1\PSTsbackupfolder\" `
| Select-Object -Property $Computers,Directory,Name} | Format-Table -AutoSize `
| Out-File -FilePath "\\Server1\Results\$Computers.txt"

If I remove | Copy-Item -Destination "\\Server1\PSTsbackupfolder\" from my script, the out-file $Computers.txt shows me the expected results, but the goal of copying over those .pst files is still a needed expectation. The out-file results for $Computers.txt: Screenshot, Any advice is appreciated.

Get-Novice
  • 11
  • 3

2 Answers2

0

You need to add -Append to Out-File to get the whole list; every time you iterate through your list you create a new file.

If you remove Copy-Item from the pipe, you send the output of Get-ChildItem to the rest of the pipe. If you keep Copy-Item there, you send the output of Copy-Item, which is just the one file it just acted on.

If you're trying to copy all files found on ServerA to \\server1\PSTBackupFolder, you're going to have a much better time running Robocopy on ServerA with something like the following:

Get-PSDrive -PSProvider FileSystem | ForEach-Objecct {robocopy $_.root *.pst /z /v /r:5 /log:\\Server1\PSTsbackupfolder\RoboCopy.$($_.Name).log \\Server1\PSTsbackupfolder}

This will get all FileSystem PSDrives, then iterate through each of the drives and robocopy all files located under the root (The drive letter) that match *.pst to \\Server1\PSTBackupfolder. We copy them in resumeable mode (/z), verbosely (/v), retry the copy 5 times (/r:5), and log all transactions to \\server1\pstbackupfolder\robocopy.DRIVELETTER.Log

We use an inline substitution ($($_.Name)) to split the copy logs into separate files because it's far easier to deal with when you're trying to figure out why Bill's PST didn't copy from the L: drive if you've only got to go through 50 entries instead of 50,000

RobbieCrash
  • 1,181
  • 9
  • 26
  • Hi RobbieCrash, thank you for the feedback. I will look into incorporating RoboCopy into my code and see if that yields better results. How do you address duplicate names? or are all your .psts names unique? My environment has many duplicate pst names as I came to find out, located in different locations. I could not think of a better way to address duplicate .pst names being copied to one container other than appending a number at end and referencing the .csv file to find it's original location. Your thoughts? – Get-Novice Mar 17 '21 at 18:40
  • Robocopy by default copy the source folder path, and will skip files that have the same name, update time and size. It will also by default overwrite older and smaller files with newer bigger ones. Copying F:\PST\Bill.pst AND G:\PST\Bill.pst to \\server1\PSTs will follow that behaviour. If you're looking at F:\Archives\Bill.PST and G:\PST\bill.pst you shouldn't have a conflict. Either way the action will be logged and you can address those after. Your updated code will work well as long as your network is reliable – RobbieCrash Mar 18 '21 at 01:02
0

I am certain there are better, cleaner, more efficient ways of writing this code, I will take your advise/suggestions and attempt to improve my code, but at my current skill level - this is what I was able to piece together which gave me the end result I was looking for.

$FileServer = hostname
$DestPath = "c:\PSTs\$FileServer"
#Checks if the Destination Dir/Path exists, if not it creates the folder and names it whatever the hostname is
If ((Test-Path -path $DestPath -PathType Container) -eq $false) {New-Item $DestPath -ItemType Directory}
#Searches for .psts files on host, exports results to .csv file.
Get-psdrive -PSProvider "FileSystem" `
| ForEach-Object {(get-childitem $_.Root -recurse -file -Filter *.PST -erroraction silentlyContinue `
| Select-Object -Property Directory,Name)} | Export-Csv -path "$DestPath\$FileServer.csv" -NoTypeInformation -Force
#Sets variable for .CSV file from previous command
$PstPath = Get-Content -Path "$DestPath\$FileServer.csv"
#Modifying .CSV for preceding commands to work.
$PSTsCSV = "$DestPath\$FileServer.csv"
($PstPath).Replace('"Directory",','"Path"').replace('"Name"','').replace('","','\') | Out-File -FilePath $PSTsCSV -force
#Reads each line of .CSV file, copies .pst file from source to destination, if file name exsists...
#in destination, it will re-name file appending a consecutive number
$CSV = Import-Csv $DestPath\$FileServer.csv
Get-ChildItem -Path $CSV.Path | ForEach-Object {
    $num=1
    $newName = Join-Path -Path $DestPath -ChildPath $_.Name
    while(Test-Path -Path $newName)
    {
       $newName = Join-Path $DestPath ($_.BaseName + "_$num" + $_.Extension)
       $num+=1
    }
    $_ | Copy-Item -Destination $newName
}

I found an issue in my script, this is the line in question. ($PstPath).Replace('"Directory",','"Path"').replace('"Name"','').replace('","','\') | Out-File -FilePath $PSTsCSV -force

If searching for .psts on a server that yields no .pst files, the output to the .csv file will have no results. An error will surface stating You cannot call a method on a null-valued expression.

You cannot call a method on a null-valued expression.
At D:\Scripts\PowerShell\tempCodeRunnerFile.ps1:6 char:1
+ ($PstPath).Replace('"Directory",','"Path"').replace('"Name"','').repl ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

to remedy [as best as I could figure out], in the event no .psts are found I re-wrote the code by adding -ErrorAction Stop so the script stops at this point, like this

Get-Content $PSTsCSV.Replace('"Directory",','"Path"').replace('"Name"','').replace('","','\') -ErrorAction Stop | Out-File -FilePath $PSTsCSV -force
Get-Novice
  • 11
  • 3