PowerShell Invoke-Command severe performance issues

Viewed 1295

I'm having a heck of a time running a script on a remote system efficiently. When run locally the command takes 20 seconds. When run using Invoke-Command the command takes 10 or 15 minutes - even when the "remote" computer is my local machine.

Can someone explain to me the difference between these two commands and why Invoke-Command takes SO much longer?

Run locally on MACHINE:get-childitem C:\ -Filter *.pst -Recurse -Force -ErrorAction SilentlyContinue

Run remotely on \MACHINE (behaves the same rather MACHINE is my local machine or a remote machine: invoke-command -ComputerName MACHINE -ScriptBlock {get-childitem C:\ -Filter *.pst -Recurse -Force -ErrorAction SilentlyContinue}

Note: The command returns 5 file objects

EDIT: I think part of the problem may be reparse points. When run locally get-childitem (and DIR /a-l) do not follow junction points. When run remotely they do, even if I use the -attributes !ReparsePoint switch)

EDIT2: Yet, if I run the command invoke-command -ComputerName MACHINE -ScriptBlock {get-childitem C:\ -Attributes !ReparsePoint -Force -ErrorAction SilentlyContinue} I don't see the Junction Points (i.e. Documents and Settings). So, it is clear that both DIR /a-l and get-childitem -attributes !ReparsePoint do not prevent it from recursing in to a reparse point. Instead it appears to only filter the actual entry itself.

Thanks a bunch!

4 Answers

It appears the issue is the Reparse Points. For some reason, access is denied to reparse points (like Documents and Settings) when the command is run locally. Once the command is run remotely, both DIR and Get-ChildItem will recurse into reparse points.

Using the -Attributes !ReparsePoint for get-childitem and the /a-l switch for DIR does not prevent this. Instead, it appears those switches only prevent the reparse point from appearing in the file listing output, but it does not prevent the command from recursing into those folders.

Instead I had to write a recursive script and do the directory recursion myself. It's a little bit slower on my machine. Instead of around 20 seconds locally, it took about 1 minute. Remotely it took closer to 2 minutes.

Here is the code I used: EDIT: With all the problems with PowerShell 2.0, PowerShell remoting, and memory usage of the original code, I had to update my code as shown below.

function RecurseFolder($path) {

    $files=@()

    $directory = @(get-childitem $path -Force -ErrorAction SilentlyContinue | Select FullName,Attributes | Where-Object {$_.Attributes -like "*directory*" -and $_.Attributes -notlike "*reparsepoint*"})
    foreach ($folder in $directory) { $files+=@(RecurseFolder($folder.FullName)) }

    $files+=@(get-childitem $path -Filter "*.pst" -Force -ErrorAction SilentlyContinue | Where-Object {$_.Attributes -notlike "*directory*" -and $_.Attributes -notlike "*reparsepoint*"})

    $files
}

If it's a large directory structure and you just need the full path names of the files you should be able to speed that up considerably by using to the legacy dir command instead of Get-ChildItem:

invoke-command -ComputerName MACHINE -ScriptBlock {cmd /c dir c:\*.pst /s /b /a-d /a-l}

Try using a remote session:

$yourLoginName = 'loginname'
$server        = 'tagetserver'
$t = New-PSSession $server -Authentication CredSSP -Credential (Get-Credential $yourLoginName)
cls
"$(get-date) - Start"
$r = Invoke-Command -Session $t -ScriptBlock{[System.IO.Directory]::EnumerateFiles('c:\','*.pst','AllDirectories')}
"$(get-date) - Finish"
Related