如何使用文本文件作为脚本块的输入 - 后台作业中的工作目录
How to use a text file as input for a scriptblock - working directory in background jobs
我的任务是编写一个 PS 脚本,该脚本将从文本文件中的机器列表中:
- 输出机器的IP地址
- 获取机器上SCCM客户端的版本
- 生成 GPResult HTMl 文件
或者
- 表示机器离线
最终规定运行在后台运行脚本(作业)
我有脚本块可以完成所有这些事情,甚至可以按照我想要的格式设置输出格式。我不能似乎要做的是让脚本块从与脚本相同的目录中调用源文件。我意识到我可以简单地对目录进行硬编码,但我希望能够 运行 在任何机器上的任何目录中进行此操作,因为我需要在多个位置使用该脚本。
有什么建议吗?
代码如下(注意:我正在尝试从其他文章中收集的东西,所以其中有一两个片段[最近的尝试是指定工作目录],但核心代码仍然存在。我也有想法首先声明脚本块,就像你在其他编程语言中对变量所做的那样,但更多的是为了可读性而不是其他任何东西):
# 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
感谢您提供的任何帮助。
干杯。
~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: (:) [测试连接], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand
+ PS计算机名:本地主机
如果我没理解错的话,我认为你遇到的问题是因为脚本块执行内部的工作目录路径不同。当您从计划任务执行脚本或将脚本传递给 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
如您所见,脚本块执行中的当前路径与您执行它的位置不同。我也看到了 Scheduled tasks 里面的路径,比如 C:\Windows\System32
.
由于您试图通过脚本块内的相对路径引用所有内容,它不会找到任何内容。一种解决方案是使用传递的参数将您的工作目录更改为已知的目录。
此外,我会使用 $PWD.Path
而不是 $PSScriptRoot
来获取当前工作目录,因为如果您 运行 来自控制台的代码,$PSScriptRoot
是空的。
正如 Theo 所观察到的,您尝试通过 -ArgumentList $wrkngdir
将所需的工作目录传递给脚本块是在正确的轨道上,但是您随后 不在脚本块中使用该参数.
只需 在脚本块的开头使用 Set-Location
即可切换到已传递的工作目录:
$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
未在提示中定义(在全局范围)):
Start-Job -ScriptBlock { Set-Location $using:PWD; Get-Location } |
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 中没有,因为 bug(该错误仍然存在于 PowerShell 7.0 中,但您可以使用 -WorkingDirectory
).
在 PowerShell(核心)7+,Start-Job
现在明智地 默认为调用者的工作目录并且还支持-WorkingDirectory
参数以简化指定工作目录。
在 PowerShell (Core) 6+ 中,您也可以使用 post-positional &
- 与 POSIX-like shell(如 bash
的方式相同 - 在这种情况下 调用者的工作目录被继承;例如:
# 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
我的任务是编写一个 PS 脚本,该脚本将从文本文件中的机器列表中:
- 输出机器的IP地址
- 获取机器上SCCM客户端的版本
- 生成 GPResult HTMl 文件 或者
- 表示机器离线
最终规定运行在后台运行脚本(作业)
我有脚本块可以完成所有这些事情,甚至可以按照我想要的格式设置输出格式。我不能似乎要做的是让脚本块从与脚本相同的目录中调用源文件。我意识到我可以简单地对目录进行硬编码,但我希望能够 运行 在任何机器上的任何目录中进行此操作,因为我需要在多个位置使用该脚本。
有什么建议吗?
代码如下(注意:我正在尝试从其他文章中收集的东西,所以其中有一两个片段[最近的尝试是指定工作目录],但核心代码仍然存在。我也有想法首先声明脚本块,就像你在其他编程语言中对变量所做的那样,但更多的是为了可读性而不是其他任何东西):
# 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
感谢您提供的任何帮助。
干杯。
~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: (:) [测试连接], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand + PS计算机名:本地主机
如果我没理解错的话,我认为你遇到的问题是因为脚本块执行内部的工作目录路径不同。当您从计划任务执行脚本或将脚本传递给 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
如您所见,脚本块执行中的当前路径与您执行它的位置不同。我也看到了 Scheduled tasks 里面的路径,比如 C:\Windows\System32
.
由于您试图通过脚本块内的相对路径引用所有内容,它不会找到任何内容。一种解决方案是使用传递的参数将您的工作目录更改为已知的目录。
此外,我会使用 $PWD.Path
而不是 $PSScriptRoot
来获取当前工作目录,因为如果您 运行 来自控制台的代码,$PSScriptRoot
是空的。
正如 Theo 所观察到的,您尝试通过 -ArgumentList $wrkngdir
将所需的工作目录传递给脚本块是在正确的轨道上,但是您随后 不在脚本块中使用该参数.
只需 在脚本块的开头使用 Set-Location
即可切换到已传递的工作目录:
$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
未在提示中定义(在全局范围)):
Start-Job -ScriptBlock { Set-Location $using:PWD; Get-Location } |
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 中没有,因为 bug(该错误仍然存在于 PowerShell 7.0 中,但您可以使用-WorkingDirectory
).
- 正在设置工作目录。对于后台作业,通过
在 PowerShell(核心)7+,
Start-Job
现在明智地 默认为调用者的工作目录并且还支持-WorkingDirectory
参数以简化指定工作目录。在 PowerShell (Core) 6+ 中,您也可以使用 post-positional
&
- 与 POSIX-like shell(如bash
的方式相同 - 在这种情况下 调用者的工作目录被继承;例如:# 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