How to use a text file as input for a scriptblock - working directory in background jobs

对着背影说爱祢 提交于 2020-04-11 11:59:22

问题


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


回答1:


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.


Generally, note that:

  • 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 7+, Start-Job 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
    



回答2:


If I understand correctly, I think that the issue you are having is because the working directory path is different inside the execution of the Script Block. This commonly happens when you execute scripts from Scheduled tasks or pass scripts to powershell.exe

To prove this, let's do a simple PowerShell code:

#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

As you can see the current path inside the execution of the script block is different than where you executed it. I have also seen inside of Scheduled tasks, paths like C:\Windows\System32 .

Since you are trying to reference everything by relative paths inside the script block, it won't find anything. One solution is to use the passed parameter to change your working directory to something known first.

Also, I would use $PWD.Path to get the current working directory instead of $PSScriptRoot as $PSScriptRoot is empty if you run the code from the console.



来源:https://stackoverflow.com/questions/53015592/how-to-use-a-text-file-as-input-for-a-scriptblock-working-directory-in-backgro

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!