Powershell 如何在后台作业中将文本文件用作scriptblock工作目录的输入

Powershell 如何在后台作业中将文本文件用作scriptblock工作目录的输入,powershell,working-directory,start-job,Powershell,Working Directory,Start Job,我的任务是编写一个PS脚本,该脚本将从文本文件中的机器列表中: 输出机器的IP地址 获取计算机上SCCM客户端的版本 生成一个GPResult HTMl文件 或 指示机器处于脱机状态 最后规定在后台运行脚本(作业) 我有一个scriptblock可以完成所有这些事情,甚至可以按照我的要求格式化输出。我似乎做不到的是让scriptblock从与脚本相同的目录中调用源文件。我意识到我可以简单地对目录进行硬编码,但我希望能够在任何机器上、任何目录中运行该脚本,因为我需要在多个位置使用该脚本 有什么建议

我的任务是编写一个PS脚本,该脚本将从文本文件中的机器列表中:

  • 输出机器的IP地址
  • 获取计算机上SCCM客户端的版本
  • 生成一个GPResult HTMl文件 或
  • 指示机器处于脱机状态
  • 最后规定在后台运行脚本(作业)

    我有一个scriptblock可以完成所有这些事情,甚至可以按照我的要求格式化输出。我似乎做不到的是让scriptblock从与脚本相同的目录中调用源文件。我意识到我可以简单地对目录进行硬编码,但我希望能够在任何机器上、任何目录中运行该脚本,因为我需要在多个位置使用该脚本

    有什么建议吗

    代码如下:(注:我在尝试从其他文章中收集的东西中间,所以它有一个或两个片段[最近的尝试是指定工作目录),但核心代码仍然存在。我还想先声明scriptblock,就像您在其他编程语言中对变量所做的那样,但更多的是为了可读性):

    我很感激你能提供的任何帮助

    干杯

    ~DavidM~

    编辑

    谢谢你的帮助。设置工作目录是可行的,但我现在遇到了一个新错误。它没有行引用,因此我不确定问题可能在哪里。下面是新代码。我已经将sriptblock移到了底部,因此它与代码的其余部分是分开的。我想那可能会更整洁一点。对于我之前的代码格式设置,我深表歉意。我将尝试用新的例子做得更好

         # Store working directory
    $getwkdir = $PWD.Path
         # Create (or clear) output file
    Write-Output "" > OnlineCheckResults.txt
         # Create subdirectory, if it does not exist. Delete and recreate if it does
    IF (Get-Item .\GPRes) {
      Remove-Item -ItemType dir "GPRes" 
      New-Item -ItemType dir "GPRes"}
    ELSE{
      New-Item -ItemType dir "GPRes"}
         # Start the job
    Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $getwkdir
         # Let job run
    Wait-Job OnlineCheck
         # Get results of job
    $results = Receive-Job OnlineCheck
         # Output results to file
    $results >> OnlineCheckResults.txt | FT Computername,IPV4Address,SCCM_Version
    
    $ScrptBlk = {
      param($wrkngdir)
      Set-Location $wrkngdir
      Get-Content Hostnames.txt | ForEach-Object {
      IF ( Test-Connection $_ -count 1 -Quiet) {
         # Get IP address, extracting only IP value
        $addr = (test-connection $_ -count 1).IPV4Address
         # Get SCCM version
        $sccm = (Get-WmiObject -NameSpace Root\CCM -Class Sms_Client).ClientVersion 
        Get-GPResultantSetOfPolicy -computer $_.name -reporttype HTML -path ".\GPRes\$_ GPResults.html"}
     ELSE {
        $addr = "Offline"
        $sccm = " "} 
    $tbl = New-Object psobject -Property @{
      Computername = $_
      IPV4Address = $addr
      SCCM_Version = $sccm}}}
    
    错误文本: 无法验证参数“ComputerName”上的参数。参数为null或为空。提供一个论点 不为null或为空,然后重试该命令。 +CategoryInfo:InvalidData:(:)[Test Connection],ParameterBindingValidationException +FullyQualifiedErrorId:ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand
    +PSComputerName:localhost

    如果我理解正确,我认为您遇到的问题是因为脚本块执行过程中的工作目录路径不同。这通常发生在从计划任务执行脚本或将脚本传递到powershell.exe时

    为了证明这一点,让我们执行一个简单的PowerShell代码:

    #Change current directory to the root of C: illustrate what's going on
    cd C:\
    Get-Location
    
    Path
    ----
    C:\
    
    
    #Execute Script Block
    $ScriptBlock = { Get-Location }
    $Job = Start-Job -ScriptBlock $ScriptBlock
    Receive-Job $Job
    
    Path
    ----
    C:\Users\HAL9256\Documents
    
    正如您所看到的,脚本块执行中的当前路径与执行它的位置不同。我还看到了计划任务的内部路径,如
    C:\Windows\System32

    因为您试图通过脚本块内的相对路径引用所有内容,所以它找不到任何内容。一种解决方案是使用传递的参数将工作目录更改为已知的目录

    另外,我将使用
    $PWD.Path
    来获取当前工作目录,而不是
    $PSScriptRoot
    ,因为如果从控制台运行代码,
    $PSScriptRoot
    是空的。

    ,您通过
    -ArgumentList$wrkngdir
    将所需的工作目录传递给脚本块,这是正确的,但是您没有在脚本块中使用该参数

    只需在脚本块的开头使用
    设置位置
    ,即可切换到传递的工作目录

    $ScrptBlk = {
      param($wrkngdir)
      
      # Change to the specified working dir.
      Set-Location $wrkngdir
    
      # ... Get-Content Hostnames.txt | ... 
    
    }
    
    # Start the job and pass the directory in which this script is located as the working dir.
    Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $PSScriptRoot
    
    PSv3+中,您可以通过使用调用方作用域中的
    $using:
    范围来简化解决方案,这允许您直接引用调用方作用域中的变量;下面是一个简化的示例,您可以直接从提示符运行(我使用
    $PWD
    作为所需的工作目录,因为
    $PSScriptRoot
    未在提示符处定义(在全局范围内)):

    如果您从(比如)C:\tmp调用上述命令,那么输出也将反映该路径,从而证明后台作业与调用者运行在同一工作目录中


    PowerShell后台作业中的工作目录

    $ScrptBlk = {
      param($wrkngdir)
      
      # Change to the specified working dir.
      Set-Location $wrkngdir
    
      # ... Get-Content Hostnames.txt | ... 
    
    }
    
    # Start the job and pass the directory in which this script is located as the working dir.
    Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $PSScriptRoot
    
    • 在PowerShell 7.0之前使用
      [environment]:GetFolderPath('MyDocuments')
      返回的目录启动后台作业,作为初始工作目录
      ,在Windows上通常是
      $HOME\Documents
      ,而在类似Unix的平台上则是
      $HOME
      (在PowerShell核心中)

      • 通过
        Start job
        -InitializationScript
        脚本块参数通过
        $using:
        引用设置后台作业的工作目录-例如,
        Start job-InitializationScript{$using:PWD}{…}
        应该可以工作,但在Windows PowerShell v5.1/PowerShell[Core]6.x中,由于(该错误仍然存在于PowerShell 7.0中,但您可以在那里使用
        -WorkingDirectory
    • PowerShell(Core)7+中,
      开始作业
      现在明智地默认为调用者的工作目录
      ,并且还支持
      -WorkingDirectory
      参数以简化工作目录的指定

    • PowerShell(Core)6+中,您也可以使用post-positional
      &
      启动后台作业-与类似POSIX的shell(如
      bash
      do)的方式相同-在这种情况下,调用方的工作目录将被继承;例如:

        # PS Core only:
        # Outputs the caller's working dir., proving that the background job 
        # inherited the caller's working dir.
        (Get-Location &) | Receive-Job -Wait -AutoRemove
      

    乍一看,您没有使用在
    -ArgumentLIst
    中发送的
    $wrkngdir
    参数。如何
    获取内容(Join Path-Path$wrkngdir-ChildPath'Hostnames.txt')
    。另外,如果我可以建议的话,请对代码缩进做些什么,因为现在它真的很难阅读..可能会有帮助
      # PS Core only:
      # Outputs the caller's working dir., proving that the background job 
      # inherited the caller's working dir.
      (Get-Location &) | Receive-Job -Wait -AutoRemove