Search code examples
powershellstart-jobscriptblock

Calling PowerShell Start-Job cmdlet with multiple parameters and foreach loop is soooo close


I have several thousand computers to backup the Security event logs to a server's share. The environment is very dynamic, hence the need to automate this.

I've been working on a script which creates a hash wherein each key is a sequence and the value for each key is N number of computers. I'm passing the keys and values to another script which will run n number of jobs to backup the logs; n will depend on how many machines I can include in each job and still process the backups efficiently.

Script 1 has this block:

foreach ($key in ($arrayAll.Keys | Sort-Object)) {
    Job-EvtLog.ps1 $key @($data)
}

Script 2 has:

Param(
    [Parameter[Mandatory=$true, ValueFromPipeline=$true)]
    [string[]] $Key,

    [Parameter[Mandatory=$true, ValueFromPipeline=$true)]
    [Array[]] $Computers
)

function job_process($key) {
    #...stuff...including casting the @($Computers) to the array: $MyComputers
    $jobCommand = [ScriptBlock]::Create("foreach(`$d in $MyComputers) {Add-Content -Path $somewhere -Value `$d}")
    Start-Job -Name $key $jobCommand -Args $somewhere $MyComputers
}

I'm testing this by trying to write the array of computers to a file, hence the Add-Content.

I'm obviously doing something wrong creating the scriptblock. Get-Job | %{$_.Command} displays:

foreach ($d in my_List_of_Hostnames) {Add-Content -Path myCorrectpath -Value $d}

Nothing is being written to myCorrectPath.

If I write:

... -Value `$d}")

toward the end of the scriptblock, the display shows the last hostname from the list of hostnames.

How do write the scriptblock such that it will iterate thru the array of hostnames in a scriptblock to process each element in one job?


Solution

  • There are situations where creating a scriptblock from a string makes sense. Yours is not one of them.

    In your code the string

    "foreach (`$d in $MyComputers) {Add-Content -Path $somewhere -Value `$d}"
    

    should be expanded to a statement like this (assuming arbitrary sample values for $MyComputers and $somewhere):

    foreach ($d in A B C) {Add-Content -Path C:\some\folder -Value $d}
    

    However, A B C is not a valid list, meaning that PowerShell would try to invoke A as a command, so your loop should produce an error like this:

    A : The term 'A' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Did you verify by collecting the job output via Receive-Job?

    Create and invoke the scriptblock like this:

    $jobCommand = {
        Param($path, $computers)
        foreach ($d in $computers) {
            Add-Content -Path $path -Value $d
        }
    }
    Start-Job -Name $key -ScriptBlock $jobCommand -Args $somewhere, $MyComputers
    

    and the code should do what you want.

    Make sure $somewhere and $MyComputers actually have the correct values.