Как использовать текстовый файл в качестве ввода для блока сценария - рабочий каталог в фоновых заданиях

Мне дали задание написать сценарий PS, который из списка машин в текстовом файле будет:

  1. Выведите IP-адрес машины
  2. Получите версию клиента SCCM на машине
  3. Создайте файл HTMl GPResult ИЛИ
  4. Укажите, что машина отключена

С окончательным условием запуска скрипта в фоновом режиме (Job)

У меня есть блок сценария, который будет делать все эти вещи, и даже формат вывода будет таким, как я хочу. То, что я не могу сделать, это заставить блок сценария вызывать исходный файл из того же каталога, что и сценарий. Я понимаю, что могу просто жестко закодировать каталоги, но я хочу иметь возможность запускать это на любом компьютере, в любом каталоге, поскольку мне нужно будет использовать сценарий в нескольких местах.

Какие-либо предложения?

Код выглядит следующим образом (Примечание: я пытаюсь понять, что я собрал из других статей, поэтому в нем есть фрагмент или два [последняя попытка заключалась в указании рабочего каталога], но основной код все еще там. Я также пришла идея сначала объявить блок сценария, как вы делаете с переменными на других языках программирования, но больше для удобства чтения, чем что-либо еще):

 # 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. Аргумент равен нулю или пуст. Укажите аргумент, который не может быть пустым или пустым, а затем попробуйте выполнить команду еще раз. + CategoryInfo: InvalidData: (:) [Test-Connection], ParameterBindingValidationException + FullyQualifiedErrorId: ParameterArgumentValidationError, Microsoft.PowerShell.Commands.TestConnectionCommand + PSComputerName: localhost


person DavidM    schedule 26.10.2018    source источник
comment
На первый взгляд, вы не используете параметр $wrkngdir, который отправляете в -ArgumentLIst. Как насчет Get-Content (Join-Path -Path $wrkngdir -ChildPath 'Hostnames.txt'). Кроме того, если я могу предложить, сделайте что-нибудь с отступом в коде, потому что теперь его действительно трудно читать ...   -  person Theo    schedule 26.10.2018
comment
Может быть полезно начать обдумывать и разрабатывать сценарии в концепции расширенной функции PowerShell. Используйте блоки Begin, Process, End. В PowerShell ISE нажмите CTRL + J, чтобы открыть фрагменты, и вставьте, чтобы просмотреть пример.   -  person user4317867    schedule 27.10.2018


Ответы (2)


Как отмечает Тео, вы на правильном пути, пытаясь передать желаемый рабочий каталог в блок скрипта через -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:, которая позволяет напрямую ссылаться на переменные в области вызывающего / em>; вот упрощенный пример, который можно запустить прямо из приглашения (я использую $PWD в качестве желаемого рабочего каталога, потому что $PSScriptRoot не определен в приглашении (в глобальной области)):

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

Если вы вызовете указанную выше команду, скажем, из C:\tmp, выходные данные также будут отражать этот путь, доказывая, что фоновое задание выполнялось в том же рабочем каталоге, что и вызывающий.


Рабочие каталоги в фоновых заданиях PowerShell:

  • До PowerShell 7.0 запуск фоновых заданий с помощью _ 9_ использует каталог, возвращенный [environment]::GetFolderPath('MyDocuments'), в качестве исходного рабочего каталога, который в Windows обычно $HOME\Documents, тогда как это просто $HOME на Unix-подобных платформах (в PowerShell Core < / em>).

    • 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).
  • В PowerShell (Core) 7+ Start-Job теперь разумно по умолчанию используется рабочий каталог вызывающего абонента, а также поддерживает -WorkingDirectory параметр для упрощения указания рабочего каталога.

  • В PowerShell (Core) 6+ вы можете альтернативно запускать фоновые задания с постпозиционным & - так же, как это делают POSIX-подобные оболочки, такие как 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
    
person mklement0    schedule 27.10.2018

Если я правильно понимаю, я думаю, что проблема, с которой вы столкнулись, заключается в том, что путь к рабочему каталогу отличается внутри выполнения блока сценария. Обычно это происходит, когда вы выполняете сценарии из запланированных задач или передаете сценарии в 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 пуст, если вы запускаете код из консоли.

person HAL9256    schedule 26.10.2018