Lambda Functions Not Executing

This topic contains 2 replies, has 3 voices, and was last updated by  Max Kozlov 2 months ago.

  • Author
    Posts
  • #73228

    Brandon Lashmet
    Participant

    One can pass function definitions as parameters to a ScriptBlock using the method described in this post (https://powershell.org/forums/topic/pass-function-definitions-to-scriptblock/), but this method only saves space in the ScriptBlock; it doesn't prevent the functions from being defined twice.

    I tried implementing actual lambda functions using this syntax:

    $TestFunction = {
    param($server)
    
    write-host $server
    
    }
    
    $TestFunction.invoke($server)
    

    This works for this simple example (the server name is returned), but implemented as follows in a larger script, the functions do not execute (i.e., running ScriptName.ps1 -ConfigFile Chef.config does not error out, but kicks back to the command prompt without doing anything).

    Any ideas why the functions wouldn't execute in this script?

    Any help is appreciated.

    
    $BootStrap = {
    
    	param($Server,$Username,$Password)
    
    	$NodeList = @(knife node list)        
    
    	if($NodeList -notcontains $server)
    
    	{
    
    
    		try {
    			
    			#knife bootstrap windows winrm $server --winrm-user $username --winrm-password $password --node-name $server --y
                Invoke-WebRequest https://devops-api.prd.costargroup.com/api/chef/bootstrap?server_name=$server
                Invoke-Command $server { chef-client }
    			Write-Host `n"$server" -ForegroundColor Cyan -NoNewline; Write-Host " Bootstrap SUCCESS" -ForegroundColor Green
    			[boolean]$BootstrapSuccess = "true"
    			return [boolean]$BootstrapSuccess
    
    		}
    
    		catch {
    
    			$ErrorMessage = $_.Exception.Message
    			Write-Host "The error message was $ErrorMessage"
    			[boolean]$BootstrapSuccess = "false"
    			return [boolean]$BootstrapSuccess
    			
    
    		}      
    
    	}
    
    	else{ Write-Host "$Server already exists in Chef, processing next server..." -ForegroundColor Yellow }
    
    }
    $RunChefClient = {
    param($server)
    
    Invoke-Command $server {chef-client}
    
    }
    $TestFunction = {
    param($server)
    
    write-host $server
    
    }
    $SetEnvironment = {
    
    	param($Server,$Environment)
    
    
    	if(!([string]::IsNullOrWhiteSpace($Environment)))
    
    	{
    
    		try{
    
    			knife node environment_set $server $environment 2> $null
    			Write-Host `n"$server" -ForegroundColor Cyan -NoNewline; Write-Host " Environment set SUCCESS" -ForegroundColor Green
    
    		}
    
    		catch
    
    		{
    
    			$ErrorMessage = $_.Exception.Message
    			Write-Host "The error message was $ErrorMessage"
    			Throw "Environment set failed, stopping script..."
    			Exit
    
    		}
    
    	}
    
    	else {Write-Host "No valid environment to set, exiting..."; exit}
    
    }
    $SetTags = {
    
    	param($Server,$Tags)
    
    	foreach ($tag in $Tags) {
    
    
    
    		try{
    
    			knife tag create $Server $Tag 2> $null
    			write-host $Server -ForegroundColor Cyan -NoNewline; write-host " $($Tag) "-ForegroundColor Yellow -NoNewline; write-host "Tag Applied Successfully" -ForegroundColor Green
    			
    
    		}
    
    		catch{
    
    			$ErrorMessage = $_.Exception.Message
    			Write-Host "The error message was $ErrorMessage"
    			Throw "Tag application failed, stopping script..."
    			Exit
    		}
    
    	}
    
    }
    $SetRoles = {
    
    	param($Server,$Roles)
    
    	foreach ($role in $Roles) {   
    
    		try{
    
    			knife node run_list add $Server role[$($role)] 2> $null
    			write-host $Server -ForegroundColor Cyan -NoNewline; write-host " $($role) "-ForegroundColor Yellow -NoNewline; write-host "Role Applied Successfully" -ForegroundColor Green			
    
    		}
    
    		catch{
    
    			$ErrorMessage = $_.Exception.Message
    			Write-Host "The error message was $ErrorMessage"
    			Throw "Role application failed, stopping script..."
    			Exit
    
    		}
    
    	}
    
    }
    $DisplayJobStatus = {
    
    	$activity = 'Bootstrapping servers in $Servers and setting Chef environment...'
    
    #Get and display job status
    	$RunningJobs = get-job | ? { $_.state -eq "running" }
    
    	$InitialRunningJobsCount = $RunningJobs.count
    
    	$CurrentRunningJobsCount = $RunningJobs.count
    
    
    	while($CurrentRunningJobsCount -gt 0) {
    
    		$PercentComplete=[math]::Round((($InitialRunningJobsCount-$CurrentRunningJobsCount)/$InitialRunningJobsCount * 100),2)
    
    		write-progress -activity "$activity" -status "Progress: $PercentComplete%" -percentcomplete (($InitialRunningJobsCount-$CurrentRunningJobsCount)/$InitialRunningJobsCount*100)
    
    		$Jobs | Receive-Job
    
    		$CurrentRunningJobsCount = (get-job | ? { $_.state -eq "running" }).Count
    		
    
    	}
    
    
    
    }
    	
    #Create bootstrap job for each server, pass lambda functions to Scriptblock for execution.
    	
    if(($Username -ne $null) -and ($Password -ne $null))
    {
    ForEach($HashTable in $Config)
    
    
    	{
    
    		$Server = $HashTable.Server
    		$Roles = $HashTable.Roles
    		$Tags = $HashTable.Tags
    		$Environment = $HashTable.Environment
    
    	
    
    		$ScriptBlock = {
    
    			param ($Server,$Roles,$Tags,$Environment,$Username,$Password,$BootStrap,$RunChefClient,$SetEnvironment,$SetTags,$SetRoles)
    
                if(($BootStrap.invoke($Server,$Username,$Password)))
    
    			{
    
    				$SetEnvironment.invoke($Server,$Environment)
    
    				$SetTags.invoke($Server,$Tags)
    
    				$SetRoles.invoke($Server,$Roles)
    
    				$RunChefClient.invoke($Server)
    
    			}
    
    
    			else {Write-Host "Bootstrap failed, processing next server..."; return}
    			
    
    		} 
    
    		$Jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList @($Server,$Roles,$Tags,$Environment,$Username,$Password,$BootStrap,$RunChefClient,$SetEnvironment,$SetTags,$SetRoles,$DisplayJobStatus)
    	}
    
    
    	#$DisplayJobStatus.invoke()
    }
    
    else {Write-Host "Username or password is missing, exiting..." -ForegroundColor Red; exit}
    
    }
    
    Main
    
    
  • #73313

    Don Jones
    Keymaster

    I think you've just aimed yourself in a coding direction that PowerShell's creators didn't really intent or anticipate. I'm personally not clear on why you wouldn't just define all of those things as functions and just call them as such. I'm also not clear on why Write-Host is in such use. My lack of clarity on these things makes me suspect I'm missing something fundamental about your question or environment, so I'm hesitant to weigh in... I'm also not clear on what doesn't work. Like, is there an error message? Does it simply do nothing?

    I suspect that the problem is one of scope, though. If I'm following this correctly, your'e starting a job which runs $Scriptblock. However, $Scriptblock does not contain the other elements you've referred to, like $SetTags. The job is sorta-kinda a new process, and it's certainly a new scope; it doesn't "inherit" the scope of its caller. Everything it knows about the world is in $scriptblock, which doesn't contain the definitions for anything. I _do_ see that you're passing those in as arguments, but because this is kind of edge-of-the-reservation for PowerShell in terms of coding patterns, I can't be sure that what you're passing in has retained its integrity as an executable block (for example).

    Don't get me wrong, I totally get how this is a common approach in some other languages. It's just not something you see much of in PowerShell.

  • #73343

    Max Kozlov
    Participant

    the problem definitely somewhere in your code or environment
    minimal example works ok
    I do not like at all 'exit' everywhere in your code

    1.ps1

    param($testparam1, $testparam2)
    $test1 = {param($test1) write-host "test1: $test1" }
    $test2 = {param($test2) write-host "test2: $test2" }
    $main = { param($sb1, $test1, $sb2, $test2 ) $sb1.Invoke($test1); $sb2.Invoke($test2) }
    & $main $test1 'internaltestparam1' $test2 'internaltestparam2'
    $main.Invoke($test1,$testparam1,$test2,$testparam2)
    
    D:\>powershell -noprofile -file d:\1.ps1 p1 p2
    test1: internaltestparam1
    test2: internaltestparam2
    test1: p1
    test2: p2
    

    ans sligtly modified example for remote execution.
    modified because powershell send scriptblock to remote server as string

    $main = { param($sb1, $test1, $sb2, $test2 ) [scriptblock]::create($sb1).Invoke($test1); [scriptblock]::create($sb2).Invoke($test2) }
    Invoke-Command -ComputerName test-serv -ScriptBlock $main -ArgumentList $test1, 'pp1', $test2, 'pp2'
    test1: pp1
    test2: pp2
    

You must be logged in to reply to this topic.