How can I call a Function, inside of a Function, inside of Invoke-Command ScriptBlock?

78 Views Asked by At

I have been racking my brain over this and scouring the internet for an answer, but I can't seem to find anything for my specific situation. I have created a set of Functions that help me add/remove users and groups from the local "Remote Desktop Users" group on Win2016 Servers remotely. By themselves, they work fine, but I am trying to add them to a menu script that I am developing so other admins can use them without having to run them manually.

The problem I am having is that in my script setup, I have created groups of logging functions that I am trying to use inside of my Add/Remove User functions, which needs to be used inside of an Invoke-Command -ScriptBlock. However, this is the error that I get:

The term 'Write-GreenConsole' 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.
    + CategoryInfo          : ObjectNotFound: (Write-GreenConsole:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
    + PSComputerName        : SERVER01

Here are two of the functions am trying to get to work together. The first one is the logging function, the second one is the Add/Remove function.

function Write-GreenConsole ( $Details ) {
    $today = Get-Date -UFormat %d%b%Y
    $LogPath = "\\Network_Path\RDP_Logs_$today.log"
    $LogDate = Get-Date -UFormat "%D %T"
    $LogLevel = "INFO"
    $Results =  Write-Output "[$LogDate] [$LogLevel] [$ServerName]: $Details"
    $Results | Out-File -FilePath $LogPath -Append
Write-Host "[$LogDate] [$LogLevel] [$ServerName]: $Details" -ForegroundColor Green -BackgroundColor Black
}
function Get-RDPGoupMembers ( $svrName ) {
  Invoke-Command -ComputerName $svrName -ScriptBlock { 
    Write-GreenConsole "[ Group members for Server $env:COMPUTERNAME =" (Get-LocalGroupMember -Group 'Remote Desktop Users').Name "]"
  }
}

The "$svrName" variable comes from the main script based on user inputs from the menu. Please let me know if you need any more details. Any ideas would be greatly appreciated, thank you!

1

There are 1 best solutions below

2
mklement0 On

A remotely executing script block shares no state with the caller, so the script block you're passing to Invoke-Command -ComputerName ... doesn't know about the caller's Write-GreenHost function.

Therefore, you must pass the function definition to the script block and recreate the function there:

function Get-RDPGoupMembers ($svrName) {
  $funcDef = ${function:Write-GreenConsole} # aux. variable that stores the function body.
  Invoke-Command -ComputerName $svrName -ScriptBlock { 
    ${function:Write-GreenConsole} = $using:funcDef # recreate the function
    Write-GreenConsole "[ Group members for Server $env:COMPUTERNAME =" (Get-LocalGroupMember -Group 'Remote Desktop Users').Name "]"
  }
}

Note:

  • The above focuses just on recreating your function as-is.

    • Separately - and this aspect is independent of whether a function is involved or not - because your function happens to access a network location, more work is needed to work around the so-called double-hop problem; one solution, which involves passing explicit credentials and using them to map an aux. drive in the remote script block, is demonstrated in this answer. Note that you needn't actually use that aux. drive: Once it is mapped, UNC paths to the same resource will start to work.
  • ${function:Write-GreenConsole} is an example of namespace variable notation: on getting the value, the function's body is returned; on setting it, the function created (or its existing body is redefined). See this answer for background information.

  • Note the use of an aux. variable, $funcDef, to store the function body in the caller's scope, which is then referenced in the remote script block via the $using: scope.

    • The reason is that trying to combine $using: with namespace variable notation currently (as of PowerShell (Core) 7.3.x) causes parser errors with function names containing - and is quietly ignored with function names that don't; that is, ${using:function:Write-GreenConsole} does not work.

    • Arguably, however, it should, especially given that it does work with Start-Job. See GitHub issue #20422.