I want to find all Package.json files on a remote system using PowerShell. In order to optimize this, I found a C# class on the internet adjusted it and now would like to use it.
This code runs really fast whenever I run it locally on my machine, however when I use the code via Invoke-Command, it's super slow on the remote machine (which is a VM on VmWare).
Here is the code:
$Result = Invoke-Command -ComputerName "RemoteSystem" -ScriptBlock {
Add-Type -TypeDefinition @"
using System;
using System.IO;
using System.Linq;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
public class FileSearch {
public struct WIN32_FIND_DATA {
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
static extern bool FindClose(IntPtr hFindFile);
static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
static BlockingCollection<string> fileList {get;set;}
public static BlockingCollection<string> GetFiles(string searchDir, string searchFile) {
bool isPattern = false;
if (searchFile.Contains(@"?") | searchFile.Contains(@"*")) {
searchFile = @"^" + searchFile.Replace(@".",@"\.").Replace(@"*",@".*").Replace(@"?",@".") + @"$";
isPattern = true;
}
fileList = new BlockingCollection<string>();
SearchDirectory(searchDir, searchFile, isPattern);
return fileList;
}
private static void SearchDirectory(string path, string searchFile, bool isPattern) {
IntPtr handle = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA fileData;
path = path.EndsWith(@"\") ? path : path + @"\";
handle = FindFirstFile(path + @"*", out fileData);
if (handle != INVALID_HANDLE_VALUE) {
FindNextFile(handle, out fileData); // Skip "." entry
while (FindNextFile(handle, out fileData)) {
if ((fileData.dwFileAttributes & 0x10) > 0) { // Directory
string fullPath = path + fileData.cFileName;
SearchDirectory(fullPath, searchFile, isPattern);
} else { // File
if (isPattern) {
if (Regex.IsMatch(fileData.cFileName, searchFile, RegexOptions.IgnoreCase)) {
string fullPath = path + fileData.cFileName;
fileList.TryAdd(fullPath);
}
} else {
if (fileData.cFileName.Equals(searchFile, StringComparison.OrdinalIgnoreCase)) {
string fullPath = path + fileData.cFileName;
fileList.TryAdd(fullPath);
}
}
}
}
FindClose(handle);
}
}
}
"@ -IgnoreWarnings
$searchFile = "package.json"
$lookupPath = "C:"
$Files = @()
$Files = @([FileSearch]::GetFiles($lookupPath,$searchFile))
return $Files
}
$Result
I rewrote the code with Get-Childitem which works fine on the Remote Machine and Locally. I tried using the Script via PsExec (copying the Script to the Remote Machine and running it there), Invoke-command, Interactive Powershell Session but the results are the same. The only things left would be to disable the Virus Protection but that is not really an Option. You can see when i run the Code locally that there is 80% Disk Usage and 30% CPU Usage but with invoke command just 30% CPU Usage and 0-1% Disk Usage. So the runtime locally (When i sign into the Remote Machine via RDP) is around 10Seconds and when ran from my machine with invoke-command on the Remote Machine is around 10-20min. Notable is also that when run locally the System Process does the Disk searching (has the high DiskUsage) and when run Remotly System doesnt even seem to be involved.
What i would like to know is just why that is. GCI gets the Job done but i dont care about that. I really really would like to know why this code is so very slow ONLY on the Remote System.
[System.IO.Directory]::EnumerateFileslearn.microsoft.com/en-us/dotnet/api/… rather than messing with PInvoke? Why are you usingAnsirather thanUnicode? Why are you usingBlockingCollectionwhen there is no parallelism? Why is your struct missing the last three (deprecated) fields?[System.IO.Directory]::EnumerateFilesif i recall stopped completely when it ran into a file it did not have permissions to read making it useless for going through all of C:\. Why ANSI.. idk was from the code i originally found on the Internet. I dont know how to code in C# I just write Powershell Code. Im using BlockingCollection because i used to use this with Multithreading but i changed the code for this purpose to the best of my abilties so BlockingCollection is just a leftover of that. Im just starting to learn C# and wanted to use that opportunity.EnumerationOptions.IgnoreInaccessiblewhich means it'll justignore those errors. So just do$opts = [System.IO.EnumerationOptions]::new(); $opts.RecurseSubdirectories = $true; $opts.IgnoreInaccessible = $true; [System.IO.Directory]::EnumerateFiles($path, $searchstring, $opts);