Unable to assign a variable within an $function definition, is there a workaround for this?
This works:
$GetADef = "function ${Task} {$function:ActualFunctionName}"
Using a variable $Var for example fails:
$GetADef = "function ${Task} {$function:$Var}"
$function:${Var}
| ~~~~~~~~~~
| Variable reference is not valid. ':' was not followed by a valid variable name
| character. Consider using ${} to delimit the name.
My undesired approach:
The following is a stripped down section where I am calling a start job for a server with a particular job I want it to run. The following works, it's reading from a queue of jobs which is a list of hash tables.
$HealthCheckJobs = foreach ($Task in $Queue) {
[string]$JobTask = 'HealthCheck' + $Task.Name
[string]$JobHost = $Task.Host
[string]$JobName = "HealthCheckJobs-" + $Task.Host + "-" + $Task.Name
# I would prefer to remove this section
Switch ($Task.Name) {
"DRIVES" { $FuncDef = "function HealthCheckDrives {$function:HealthCheckDRIVES}" }
"CPU" { $FuncDef = "function HealthCheckCPU {$function:HealthCheckCPU}" }
}
# end of what I want to remove
Start-Job -Name $JobName {
. ([scriptblock]::Create($using:FuncDef))
return &$using:JobTask -Target $using:JobHost
}
}
$Results += $HealthCheckJobs | Receive-Job -Wait # => Collect
UPDATE: Suggested Solution
Thank you for your help! Using the suggested solution, I put together a working example. The example shows threads starting by number and finally printing the results from the jobs. Additionally I added "MaxWorker" check that controls to some degree the number of concurrent threads. That was a quick hack that could probably be improve upon.
Sample working code:
function HealthCheckDrive {
param($Target)
start-sleep -Seconds 10
Return "[HealthCheckDrive]: " + $Target + " - ID - " + [string]([guid]::NewGuid()).Guid; # just for display, no meaning
}
function HealthCheckCPU {
param($Target)
start-sleep -Seconds 10
Return "[HealthCheckCPU]: " + $Target + " - ID - " + [string]([guid]::NewGuid()).Guid; # just for display, no meaning
}
$Targets = @("Server1", "Server2", "Server3", "Server4", "Server5", "Server6", "Server7", "Server8")
$Queue = @()
ForEach ($Target in $Targets) {
$Queue += [pscustomobject]@{
Action = 'HealthCheckDrive' # Function to Invoke here
Host = $Target
}
$Queue += [pscustomobject]@{
Action = 'HealthCheckCPU' # Function to Invoke here
Host = $Target
}
}
$isShowProgress = $true
If ($isShowProgress) {
Write-Host("Launching: [" + $Queue.Length + "] Workers:`n [") -NoNewline
[int]$C = 1
}
[string]$JobName = "SystemCheckJobs" # giving jobs the same name for rough accounting
[int]$MaxWorkers = 9 #arbitrary limit for concurrent jobs
# This hash is used to hold each function definition so that if
# the same function is invoked more than once there there is no need
# to `Get-Command` more than once
$commandDefinitions = @{}
$WorkerJobs = foreach ($Task in $Queue) {
# If we already have the Definition for this Action
if($commandDefinitions.ContainsKey($Task.Action)) {
$def = $commandDefinitions[$Task.Action] # use it
}
else {
# else, get it and set it
$def = (Get-Command $Task.Action -CommandType Function).Definition
$commandDefinitions[$Task.Action] = $def
}
If ($isShowProgress) {
Write-Host(" " + $C) -NoNewline ; $C += 1 ; Start-Sleep -Milliseconds (Get-Random -Minimum 50 -Maximum 200) # visual
}
Start-Job -Name $JobName {
& ([scriptblock]::Create($using:def)) -Target $using:Task.Host
}
# wait to start new jobs until count goes down (or this times out)
$AutoTimeOut = 0
While ($AutoTimeOut -lt 10) {
$running = Get-Job | Where-Object { $_.Name.Contains($JobName) -And $_.State.Contains("Running") }
if ($running.Count -ge $MaxWorkers) {
Start-Sleep -Seconds 1
$AutoTimeOut += 1
} Else {
Break
}
}
}
If ($isShowProgress) {Write-Host " ] (Please Wait)"}
$WorkerJobs | Receive-Job -Wait -AutoRemoveJob # => Collect