如何使用文本文件作为脚本块的输入 - 后台作业中的工作目录 [英] How to use a text file as input for a scriptblock - working directory in background jobs

查看:76
本文介绍了如何使用文本文件作为脚本块的输入 - 后台作业中的工作目录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  1. 输出机器的IP地址
  2. 获取机器上 SCCM 客户端的版本
  3. 生成 GPResult HTML 文件或
  4. 表示机器离线

最后规定在后台运行脚本(Job)

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

有什么建议吗?

代码如下(注意:我正在尝试从其他文章中收集的东西,所以里面有一两个片段[最近的尝试是指定工作目录],但核心代码仍然是那里.我也有先声明脚本块的想法,就像你在其他编程语言中对变量所做的那样,但更多的是为了可读性):

 # 作业中要处理的命令列表$ScrptBlk = {参数($wrkngdir)获取内容主机名.txt |ForEach-Object {# 查看Host是否在线IF ( 测试连接 $_ -count 1 -Quiet) {# 获取IP地址,只提取IP值$addr = (test-connection $_ -count 1).IPV4Address# 获取 SCCM 版本$sccm = (Get-WmiObject -NameSpace Root\CCM -Class Sms_Client).ClientVersion# 生成 GPResult HTML 文件Get-GPResultantSetOfPolicy -computer $_.name -reporttype HTML -path ".\GPRES\$_ GPResults.html"}别的 {$addr = "离线"$sccm = " "}$tbl = 新对象 psobject -Property @{计算机名 = $_IPV4Address = $addrSCCM_Version = $sccm}}}# 创建(或清除)输出文件回声">在线检查结果.txt# 创建子目录,如果不存在IF (-Not (Get-Item .\GPRes)) { New-Item -ItemType dir ".\GPres" }# 获取当前工作目录$wrkngdir = $PSScriptRoot# 执行脚本Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $wrkngdir# 让作业运行等待工作在线检查# 获取作业结果$results = Receive-Job OnlineCheck# 输出结果到文件$results >>OnlineCheckResults.txt |FT 计算机名、IPV4 地址、SCCM_版本

感谢您提供的任何帮助.

干杯.

~DavidM~

编辑

感谢大家的帮助.设置工作目录有效,但我现在收到一个新错误.它没有线路参考,所以我不确定问题可能出在哪里.下面的新代码.我已将 sriptblock 移到底部,因此它与其余代码分开.我想那可能会更整洁一些.对于我之前的代码格式,我深表歉意.我将尝试用新示例做得更好.

 # 存放工作目录$getwkdir = $PWD.Path# 创建(或清除)输出文件写输出"" >在线检查结果.txt# 创建子目录,如果它不存在.删除并重新创建(如果有)IF (Get-Item .\GPres) {Remove-Item -ItemType dir "GPres"New-Item -ItemType dir "GPRS"}别的{New-Item -ItemType dir "GPRS"}# 开始工作Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $getwkdir# 让作业运行等待工作在线检查# 获取作业结果$results = Receive-Job OnlineCheck# 输出结果到文件$results >>OnlineCheckResults.txt |FT 计算机名、IPV4 地址、SCCM_版本$ScrptBlk = {参数($wrkngdir)设置位置 $wrkngdir获取内容主机名.txt |ForEach-Object {IF ( 测试连接 $_ -count 1 -Quiet) {# 获取IP地址,只提取IP值$addr = (test-connection $_ -count 1).IPV4Address# 获取 SCCM 版本$sccm = (Get-WmiObject -NameSpace Root\CCM -Class Sms_Client).ClientVersionGet-GPResultantSetOfPolicy -computer $_.name -reporttype HTML -path ".\GPRES\$_ GPResults.html"}别的 {$addr = "离线"$sccm = " "}$tbl = 新对象 psobject -Property @{计算机名 = $_IPV4Address = $addrSCCM_Version = $sccm}}}

错误文本:无法验证参数ComputerName"上的参数.参数为 null 或为空.提供一个论点不为 null 或为空,然后重试该命令.+ CategoryInfo : InvalidData: (:) [Test-Connection], ParameterBindingValidationException+ FullQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand+ PSComputerName : 本地主机

解决方案

正如 Theo 所观察到的,您在通过尝试通过 -ArgumentList $wrkngdir 将所需的工作目录传递给脚本块来正确跟踪,但是您没有在脚本块中使用该参数.>

只需在脚本块的开头使用 Set-Location 来切换到传递的工作目录:

$ScrptBlk = {参数($wrkngdir)# 切换到指定的工作目录.设置位置 $wrkngdir# ... Get-Content Hostnames.txt |...}# 启动作业,将这个脚本所在的目录作为工作目录传递.Start-Job -name OnlineCheck"-ScriptBlock $ScrptBlk -ArgumentList $PSScriptRoot

PSv3+中,您可以通过使用$using:作用域来简化解决方案,它允许您在调用者范围直接;这是一个简化的示例,您可以直接从提示符运行(我使用 $PWD 作为所需的工作目录.因为 $PSScriptRoot 未在提示(在全局范围内)):

Start-Job -ScriptBlock { Set-Location $using:PWD;获取位置 } |Receive-Job -Wait -AutoRemove

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


PowerShell 后台作业中的工作目录:

  • 在 PowerShell 7.0 之前,使用 Start-Job 使用 [environment]::GetFolderPath('MyDocuments') 返回的目录作为初始工作目录,在 Windows 上通常是 $HOME\Documents,而在类 Unix 平台上它只是 $HOME(在 PowerShell Core).

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

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

     # 仅 PS 核心:# 输出调用者的工作目录,证明后台作业# 继承调用者的工作目录.(获取位置 &) |Receive-Job -Wait -AutoRemove

I have been given the task to write a PS script that will, from a list of machines in a text file:

  1. Output the IP address of the machine
  2. Get the version of the SCCM client on the machine
  3. Produce a GPResult HTMl file OR
  4. Indicate that the machine is offline

With a final stipulation of running the script in the background (Job)

I have the scriptblock that will do all of these things, and even have the output formatted like I want. What I cannot seem to do, is get the scriptblock to call the source file from within the same directory as the script. I realize that I could simply hard-code the directories, but I want to be able to run this on any machine, in any directory, as I will need to use the script in multiple locations.

Any suggestions?

Code is as follows (Note: I am in the middle of trying stuff I gathered from other articles, so it has a fragment or two in it [most recent attempt was to specify working directory], but the core code is still there. I also had the idea to declare the scriptblock first, like you do with variables in other programming languages, but more for readability than anything else):

 # List of commands to process in job
$ScrptBlk = {
param($wrkngdir)

Get-Content Hostnames.txt | ForEach-Object {
 # Check to see if Host is online
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
 # Generate GPResult HTML file 
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}}}
 # Create (or clear) output file
Echo "" > OnlineCheckResults.txt
 # Create subdirectory, if it does not exist
IF (-Not (Get-Item .\GPRes)) { New-Item -ItemType dir ".\GPRes" }
 # Get current working directory
$wrkngdir = $PSScriptRoot
 # Execute script
Start-Job -name "OnlineCheck" -ScriptBlock $ScrptBlk -ArgumentList $wrkngdir
 # 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

I appreciate any help you may have to offer.

Cheers.

~DavidM~

EDIT

Thanks for all the help. Setting the working directory works, but I am now getting a new error. It has no line reference, so I am not sure where the problem might be. New code below. I have moved the sriptblock to the bottom, so it is separate from the rest of the code. I thought that might be a bit tidier. I do apologize for my earlier code formatting. I will attempt to do better with the new example.

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

Error text: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again. + CategoryInfo : InvalidData: (:) [Test-Connection], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand + PSComputerName : localhost

解决方案

As Theo observes, you're on the right track by trying to pass the desired working directory to the script block via -ArgumentList $wrkngdir, but you're then not using that argument inside your script block.

All it takes is to use Set-Location at the start of your script block to switch to the working directory that was passed:

$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

In PSv3+, you can simplify the solution by using the $using: scope, which allows you to reference variables in the caller's scope directly; here's a simplified example, which you can run directly from the prompt (I'm using $PWD as the desired working dir., because $PSScriptRoot isn't defined at the prompt (in the global scope)):

Start-Job -ScriptBlock { Set-Location $using:PWD; Get-Location } |
  Receive-Job -Wait -AutoRemove

If you invoke the above command from, say, C:\tmp, the output will reflect that path too, proving that the background job ran in the same working directory as the caller.


Working directories in PowerShell background jobs:

  • Before PowerShell 7.0, starting background jobs with Start-Job uses the directory returned by [environment]::GetFolderPath('MyDocuments') as the initial working directory, which on Windows is typically $HOME\Documents, whereas it is just $HOME on Unix-like platforms (in PowerShell Core).

    • Setting the working dir. for the background job via Start-Job's -InitializationScript script-block argument via a $using: reference - e.g., Start-Job -InitializationScript { $using:PWD } { ... } should work, but doesn't in Windows PowerShell v5.1 / PowerShell [Core] 6.x, due to a bug (the bug is still present in PowerShell 7.0, but there you can use -WorkingDirectory).
  • In PowerShell (Core) 7+, Start-Job now sensibly defaults to the caller's working directory and also supports a -WorkingDirectory parameter to simplify specifying a working directory.

  • In PowerShell (Core) 6+ you can alternatively start background jobs with a post-positional & - the same way that POSIX-like shells such as bash do - in which case the caller's working directory is inherited; e.g.:

      # 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
    

这篇关于如何使用文本文件作为脚本块的输入 - 后台作业中的工作目录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆