使用Start-Job时本地函数调用不起作用

时间:2018-04-14 13:49:14

标签: powershell

       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
       }

在下面的代码片段中,我想开始一个工作并为while循环的每次迭代运行F1。虽然在F1中定义的工作正常,但是当通过ScriptBlock完成时,F1到F2的调用似乎没有完成。

这里有什么我想念的吗?

更新1

我试图实现你的建议,以实现一些相当简单和直接的东西,使用作业同时在目录中创建文件。

$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 

这里的问题是工作失败了(我在下面贴了其中一个),不知道我在这里做错了什么?

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

更新2

我包含了一个Receive-Job,这只是错误的一部分。我想弄明白但是把它贴在这里,希望你能帮助我。

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)

2 个答案:

答案 0 :(得分:2)

Start-Job的作用是在后端创建一个单独的线程,在线程中初始化另一个默认的Powershell会话,并运行使用参数-ScriptBlock传入的命令。

新线程中的会话是一个全新的会话,它没有加载任何非默认模块,变量或函数。它有自己的变量范围和生命周期。所以我不确定是否可以让它使用默认会话中定义的功能,但至少我不容易看到它。

我可以提供的一个解决方法是使用参数-InitializationScript定义函数F2,如下所示

       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
       }

或者,如果你必须定义多个函数,比如F1,需要调用F2,并希望以某种方式重用F2的定义代码。我们可以为F2

创建一个ScriptBlock对象
       $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
       }

致以最诚挚的问候,

答案 1 :(得分:1)

这实际上是same answer as Dong Mao's, which was here first,但是我在副本上写了它而没有意识到这就在这里。

无论如何,这种方法(使用模块)可以更好地解决代码问题的扩展和重复问题。

并非F2超出范围,而且它甚至不存在于工作中。

作业是作为单独的进程运行的,因此除非您明确指定,否则调用进程中不会包含任何内容。

当您引用${function:F1}时,您正在使用PSDrive形式的变量。实质上,它相当于Get-Content -LiteralPath Function:\F1

如果您查看返回的内容,您会看到1)它是[ScriptBlock](这正是Start-Job' s所需的类型-ScriptBlock {1}}参数),但您也会看到它是函数的内容;它甚至不包含名称。

这意味着在作业中你直接执行函数体但你实际上并没有调用函数。

您可以解决此问题,重新生成函数定义部分,定义函数,或者只在脚本块中重新定义F2。这将很快变得混乱。

使用模块

我的建议是将您的功能分解为系统中可用的模块,然后在脚本块中,只需调用Import-Module MyFunctions即可。

如果您不想完成具有单独文件的过程,并希望将其保留,则可以动态创建模块。只需在脚本中将您的功能定义为模块,如果愿意,可以在作业中再次执行。像这样:

$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
}