0

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?

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
GaryG
  • 13
  • 5

2 Answers2

2

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.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
1

Ok, let's start at the top of script 2: Parameters

This is a type cast for string: [string]
This is a type cast for an array of strings: [string[]]

Are you expecting $key to be an array of strings, or just one string, because you're only passing one string to it. Same concept applies for $Computers expecting an array of arrays.

Also, you have two things accepting their value from the pipeline, which is only going to confuse things. Perhaps instead you should just leave that out, or alternatively change it to ValueFromPipelineByPropertyName, which is an awesome option if you're going to pipe things to other things.

Next, you have a function that takes 1 parameter. In that function you use several variables, and make a scriptblock the hard way, this just doesn't seem wise. I think possibly a better way to do this is:

Param(
[Parameter(Mandatory)]
[string] $Key,
[Parameter(Mandatory)]
[string[]] $Computers)

#...stuff...including casting the @($Computers) to the array: $MyComputers
$jobCommand = {
    Param($JobPath,$JobComputers)
    foreach($d in $JobComputers) {add-content -Path $JobPath -Value $d}
}
start-job -name $key -scriptblock $jobCommand -argumentlist $somewhere $MyComputers

Then you can call it such as:

foreach ($key in ($arrayAll.Keys | Sort-Object)) {
    Job-EvtLog.ps1 -Key $key -Computers $arrayAll[$key]
}
TheMadTechnician
  • 34,906
  • 3
  • 42
  • 56
  • In my original post, I left a line out. My call had this line: $data = $arrayAll.item($key). I think it's net effect is similar to your suggestion albeit not as clean. I changed the param block as you indicated. Thanks. – GaryG Aug 23 '17 at 12:20