Author Posts

June 17, 2017 at 6:12 am

How can I call a function defined outside of function Main, within a ScriptBlock inside Main?

I tried passing the function itself as a parameter with no luck (see below).

I'd prefer to not re-define the functions within the ScriptBlock if possible.

Thank you for any help.

Function BootStrap {

	param($Server,$Username,$Password)
	Do stuff...
        return [boolean]BootstrapSuccess

}

Function SetEnvironment {

	param($Server,$Environment)
	Do stuff...
}

Function Main{

#Create bootstrap job for each server
	ForEach($HashTable in $Config)


	{

		$Server = $HashTable.Server
		$Roles = $HashTable.Roles
		$Tags = $HashTable.Tags
		$Environment = $HashTable.Environment		

		$ScriptBlock = {

			param ($Server,$Roles,$Tags,$Environment,$Username,$Password,${function:Bootstrap})


			if(BootStrap -Server $Server -Username $Username -Password $Password)

			{

				SetEnvironment -Server $Server -Environment $Environment

				SetTags -Server $Server -Tags $Tags

				SetRoles -ServerRoleMapping $ServerRoleMapping

				RunChefClient -Server $server

			}


			else {Write-Host "Bootstrap failed, exiting..."; return}
			

		}

		$Jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList @($Server,$Roles,$Tags,$Environment,$Password,${function:Bootstrap})
	}


	DisplayJobStatus

}



}

June 17, 2017 at 8:12 pm

Looks like one can pass the definition of the function as a parameter to the ScriptBlock as follows.

This is a little messy, but at least the entire function definition isn't present inside the ScriptBlock.

Any better ways of doing this?

Function Main {


    $jobs = @()
	$Config = Get-Content Chef.config -Raw | Out-String | Invoke-Expression



#Create bootstrap job for each server
	ForEach($HashTable in $Config)


	{

		$Server = $HashTable.Server
		$Roles = $HashTable.Roles
		$Tags = $HashTable.Tags
		$Environment = $HashTable.Environment

        $SetRolesDef = "function SetRoles { ${function:SetRoles} }"

		#; function SetTags { ${function:SetTags} }

		$ScriptBlock = {

			param ($Server,$Roles,$Tags,$Environment,$SetRolesDef)
             
             . ([ScriptBlock]::Create($SetRolesDef))


			if("1" -eq "1")

			{

				SetRoles $Server $Roles

				#SetTags $Server $Tags

			}


			else {Write-Host "Hmmm..."; return}
			

		} 

		$Jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList @($Server,$Roles,$Tags,$Environment,$SetRolesDef)
	}


	DisplayJobStatus

}

June 17, 2017 at 8:23 pm

Ah, even better, one can define all of the functions desired in one go...

$FunctionDefs = "function SetRoles { ${function:SetRoles} } ; function SetTags { ${function:SetTags} }"

		$ScriptBlock = {

			param ($Server,$Roles,$Tags,$Environment,$FunctionDefs)
             
             . ([ScriptBlock]::Create($FunctionDefs))


			if("1" -eq "1")

			{

				SetRoles $Server $Roles

				SetTags $Server $Tags

			}


			else {Write-Host "Hmmm..."; return}

June 20, 2017 at 3:42 pm

I see from your other question why you're trying this. As a note, when you are the first reply to your own post, you come off the "topics with no replies" list, which is what most of us check for posts needing replies. Apologies for not replying to this because of that.

I don't know that this is really a first-class use case in PowerShell in terms of the shell's design. It's not a full programming language in the sense you may be thinking of; it's very specifically an administrative shell. It's starting to take steps toward being a more first-class coding language with classes and whatnot, but what you're running against seems to largely be running against the edge of PowerShell's patterns.

Me, I'd probably take a run at defining a class instead. It's a more portable "object" than a function or a script block. You an instantiate it, and pass that concrete instance elsewhere. However, because this is a scripting language, scope is still very much a concern. If the class definition goes out of scope, then obviously any references to it won't work. That's kind of the nature of a somewhat loosely-typed scripting language; it's not like PowerShell can add classes to the .NET GAC so they'll be universally available.