Reputation: 2207
function F2([String]$var2)
{
.........
.........
}
function F1([String]$var1)
{
.........
F2 $var2
.........
}
..................
..................
while ($i -le $count)
{
F1 "dir$i"
Start-Job -ScriptBlock ${function:F1} -ArgumentList @($i)
$i = $i + 1
}
In the following snippet, I'd like to start a job and run F1 for every iteration of the while loop. Although what's defined in F1 works fine, the call by F1 to F2 doesn't seem to go through when done via the ScriptBlock.
Is there something I'm missing here ?
UPDATE 1
I tried to implement your suggestion to achieve something rather simple and straightforward, create files in directories simultaneously using jobs.
$dirname = "E:\TEST3\dir_"
$filename = "file_"
$i=1
$moduledef = {
function makeFiles([String]$dirname)
{
for ($i -le 5000)
{
echo "WASSSUP !!" >> "$dirnaname\$filename$i.txt"
$i++
}
}
function makeDir([String]$dirname)
{
mkdir "$dirname$i"
makeFiles "$dirname$i"
$i++
}
} # END OF $moduledef
New-Module -Name MyFunctions -ScriptBlock $moduledef
while($i -le 10)
{
Start-Job -ScriptBlock {
param([String]$jobArg)
New-Module -Name MyFunctions -ScriptBlock $Using:moduledef
makeDir $jobArg
} -ArgumentList @("$dirname$i")
$i++
}
Get-Job | Wait-Job
The issue here is that the jobs failed (I've pasted one of them below), any idea what I'm doing wrong here ?
HasMoreData : True
StatusMessage :
Location : localhost
Command :
param([String]$jobArg)
New-Module -Name MyFunctions -ScriptBlock $Using:moduledef
makeDir $jobArg
JobStateInfo : Failed
Finished : System.Threading.ManualResetEvent
InstanceId : e79a6489-cad3-47a6-b6f4-8a207d32c187
Id : 79
Name : Job79
ChildJobs : {Job80}
PSBeginTime : 4/14/2018 9:52:01 PM
PSEndTime : 4/14/2018 9:52:02 PM
PSJobTypeName : BackgroundJob
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
Information : {}
State : Failed
UPDATE 2
I included a Receive-Job and this is just a part of the error. I'm trying to figure it out but pasting it here hoping you can help me out.
PSPath : Microsoft.PowerShell.Core\FileSystem::E:\TEST3\dir10
PSParentPath : Microsoft.PowerShell.Core\FileSystem::E:\TEST3
PSChildName : dir10
PSDrive : E
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : True
Mode : d-----
BaseName : dir10
Target : {}
LinkType :
RunspaceId : 434aa380-b888-4ca5-897e-75a88e1f6560
Name : dir10
FullName : E:\TEST3\dir10
Parent : TEST3
Exists : True
Root : E:\
Extension :
CreationTime : 4/14/2018 9:29:12 PM
CreationTimeUtc : 4/14/2018 3:59:12 PM
LastAccessTime : 4/14/2018 9:29:12 PM
LastAccessTimeUtc : 4/14/2018 3:59:12 PM
LastWriteTime : 4/14/2018 9:29:12 PM
LastWriteTimeUtc : 4/14/2018 3:59:12 PM
Attributes : Directory
The term 'makeFiles' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: (makeFiles:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : localhost
Cannot bind parameter 'ScriptBlock'. Cannot convert the "
function makeFiles([String]$dirname)
{
for ($i -le 5000)
{
echo "WASSSUP !!" >> "$dirnaname\$filename$i.txt"
$i++
}
}
function makeDir([String]$dirname)
Upvotes: 1
Views: 2166
Reputation: 47792
This is practically the same answer as Dong Mao's, which was here first, but I wrote it on the duplicate without realizing this was here.
Anyway, this method (using modules) may better address the scaling and duplication of code issue.
It's not so much that F2
is out of scope, it's that it doesn't even exist in the job.
Jobs are run as separate processes, so nothing is included from the calling process unless you explicitly give it.
When you reference ${function:F1}
you're using the PSDrive form of variable. In essence, it is the equivalent of Get-Content -LiteralPath Function:\F1
.
If you look at what is returned, you'll see that 1) it's a [ScriptBlock]
(which is exactly the type you need for Start-Job
's -ScriptBlock
parameter), but you'll also see that it is the function's contents; it doesn't even include the name.
This means that in the job you're directly executing the body of the function but you are not actually calling a function.
You could work around this re-generating the function definition part, defining the functions, or just defining F2
over again in the script block. This will start to get messy quickly.
My recommendation is to break your functions out into a module that is available on the system, then inside the script block, just call Import-Module MyFunctions
and they will be available.
If you don't want to go through that process of having separate files, and want to keep it contained, you can create a module on the fly. Just define your functions as a module in your script, and if you like, do it again in the job. Something like this:
$moduleDefinition = {
function F2([String]$var2)
{
"~$var2~"
}
function F1([String]$var1)
{
F2 $var1
}
}
# this makes the module loaded in this context
New-Module -Name MyFunctions -ScriptBlock $moduleDefinition
while ($i -le $count)
{
F1 "dir$i"
Start-Job -ScriptBlock {
param([String]$jobArg)
$modDef = [ScriptBlock]::Create($Using:moduleDefinition)
New-Module -Name MyFunctions -ScriptBlock $modDef
F1 $jobArg
} -ArgumentList @($i)
$i = $i + 1
}
Upvotes: 1
Reputation: 361
What a Start-Job does is to create a separate thread in the backend, initial another default Powershell session in the thread and run the commands passed in with the parameter -ScriptBlock.
The session in the new thread is a fresh session, it doesn't have any non-default module, variable or function loaded in. It has its own variable scoping and life cycle. So I'm not sure if it's possible to let it use the function defined in the default session, but at least it's not easy as I can see.
One workaround I can provide is to use the parameter -InitializationScript to define the function F2 like below
function F1([String]$var1)
{
.........
F2 $var2
.........
}
..................
..................
while ($i -le $count)
{
F1 "dir$i"
Start-Job -ScriptBlock ${function:F1} -ArgumentList @($i) -InitializationScript {function F2([String]$var2){.........}}
$i = $i + 1
}
Alternatively, if you have to define multiple functions like F1 that needs to call F2 and want to somehow reuse the code of F2's definition. We can create a ScriptBlock object for F2
$InitializationScript = [ScriptBlock]::Create('function F2([String]$var2){.........}')
function F1([String]$var1)
{
.........
F2 $var2
.........
}
..................
..................
while ($i -le $count)
{
F1 "dir$i"
Start-Job -ScriptBlock ${function:F1} -ArgumentList @($i) -InitializationScript $InitializationScript
$i = $i + 1
}
Best regards,
Dong
Upvotes: 2