I'm trying to get the dll and exe file names in the windows system directory (C:\Windows\System32).
There are too many files in that path, so I'm trying to use multithreading.
However, when comparing(at "IF" statement) extensions in ScriptBlock added by Addscript, only "False" result occurs, so the file name cannot be added into the list.
if ($extensionList -contains $tmpExtension.ToString())
Maybe the conditional statement is wrong or the ScriptBlock is wrong? The conditional statement works normally when multithreading is not used.
Entire code(Powershell 5.1) :
$extensionList = @(".DLL",".EXE")
$systemDir = "C:\Windows\System32\"
$threadNumber = 4 #threads
$tempList = @()
$ScriptBlock = {
Param ($tmpFilePath)
$tmpExtension = [IO.Path]::GetExtension($tmpFilePath)
if ($extensionList -contains $tmpExtension.ToString()) {
Return $tmpFilePath
}
Return $null
}
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $threadNumber)
$RunspacePool.Open()
$Jobs = @()
$tmpfileNameList = (Get-ChildItem -Path $systemDir -File) | select-object Name
foreach($tmpfileName in $tmpfileNameList) {
$tmpFilePath = $systemDir $tmpfileName.Name
$Job = [powershell]::Create().AddScript($ScriptBlock).AddArgument($tmpFilePath)
$Job.RunspacePool = $RunspacePool
$Jobs = New-Object PSObject -Property @{
Pipe = $Job
Result = $Job.BeginInvoke()
}
}
while ($Jobs.IsCompleted -contains $false) {Start-Sleep -Milliseconds 100}
$Results = @()
ForEach ($Job in $Jobs){
if ($Job.Pipe.EndInvoke($Job.Result) -ne $null) {
$tempList = $Job.Pipe.EndInvoke($Job.Result)
}
}
foreach($tmp in $tempList) {
Write-Host $tmp
}
CodePudding user response:
The main issue is that your powershell
instance has no idea what $extensionList
is since it's never being passed as an argument hence it is always evaluating to:
$null -contains [IO.Path]::GetExtension($tmpFilePath)
If your intent is to return the FullName
of the files where it's extension is .dll
or .exe
your scriptblock
should be like this:
$ScriptBlock = {
Param ($tmpFilePath, $extensionList)
if ($extensionList -contains [IO.Path]::GetExtension($tmpFilePath))
{
$tmpFilePath
}
}
And the foreach
loop would look like this:
$tmpfileNameList = (Get-ChildItem -Path $systemDir -File).FullName
$params = @{
extensionList = $extensionList
}
$jobs = foreach($path in $tmpfileNameList)
{
$params.tmpFilePath = $path
$Job = [powershell]::Create().AddScript($ScriptBlock).AddParameters($params)
$Job.RunspacePool = $RunspacePool
[pscustomobject]@{
Pipe = $Job
Result = $Job.BeginInvoke()
}
}
See PowerShell.AddParameters Method
for details.
Then this line is wrong:
while ($Jobs.IsCompleted -contains $false) {
Start-Sleep -Milliseconds 100
}
Should be changed too:
while ($Jobs.Result.IsCompleted -contains $false) {
Start-Sleep -Milliseconds 100
}
Lastly, you can get the results of the runspaces like:
$results = $jobs.ForEach({ $_.Pipe.EndInvoke($_.Result) })
PS /> $results
In addition to whats mentioned above, the logic needs to be improved for the script to be actually efficient.
Instead of passing one path to each powershell
instance, you should split the array
of paths ($tmpfileNameList
) into chunks and give each instance a chunk to process.
You can use something like this to split your array
:
$groupSize = [math]::Ceiling($tmpfileNameList.Count / $threadNumber)
$counter = [pscustomobject]@{ Value = 0 }
$groups = $tmpfileNameList | Group-Object -Property {
[math]::Floor($counter.Value / $groupSize)
}
In this case, since you're using 4
threads, $groups
will contain 4 groups. If you want to apply this technique to your code you can copy the logic from this link.