Pass Function Definitions to ScriptBlock

This topic contains 3 replies, has 2 voices, and was last updated by  Don Jones 5 months ago.

  • Author
    Posts
  • #73099

    Brandon Lashmet
    Participant

    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
    
    }
    
    
    
    }
    
  • #73114

    Brandon Lashmet
    Participant

    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
    
    }
    
  • #73115

    Brandon Lashmet
    Participant

    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}
  • #73315

    Don Jones
    Keymaster

    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.

You must be logged in to reply to this topic.