1

I have written a custom Write-Log Function. If I use the Write-Log function inside the Invoke, I am getting below error message "Error on remote execution: The term 'Write-Log' 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."

I'm using Powershell 5.1. I did try to use ${function:Write-Log} but it didn't work.

Function Write-Log([string]$ComputerList,$message, $level="INFO") {
    $date_stamp = Get-Date -Format s
    $log_entry = "$date_stamp - $level - $message"
    $log_file = "$tmp_dir\upgrade_powershell.log"
    Write-Verbose -Message $log_entry
    Add-Content -Path $log_file -Value $log_entry
}

Function Start-Process ($ComputerList) {    
    Return Invoke-Command -computername $Computer -ScriptBlock {
                Param($file)
                $Application = $args[0]
                $ApplicationName = $Application.Substring($Application.LastIndexOf('\')+1)
                $ApplicationFolderPath = $Application.Substring(0,$Application.LastIndexOf('\'))
                $ApplicationExt = $Application.Substring($Application.LastIndexOf('.')+1)
    Write-Log -message "Installing $file on $($env:COMPUTERNAME)"
    $p = Start-Process $file -Wait -Passthru

    $p.WaitForExit()
                $p.WaitForExit()
                if ($p.ExitCode -ne 0) {
                    Write-Log -message "Failed installing with error code $($p.ExitCode)"  -level "ERROR"
                    $Return = $($env:COMPUTERNAME)
                }
                else{
                    $Return = 0

        if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) {
        $log_msg = "$($error_msg): exit code $p.ExitCode"
        Write-Log  -message $log_msg -level "ERROR"
        #throw $log_msg
        return
      }
    if ($p.ExitCode-eq 3010) {
        Reboot-AndResume
        break
    }}


}
}
Venkat
  • 11
  • 1
  • 3
  • Start-Process is a reserved word that you shouldn't use for the name of your function. That's not the problem, though. Your code is formatted with inconsistent alignment, and it's hard to read your intent from it. I wasn't able to reproduce your issue. Maybe there was a copy-paste error. – Rich Moss Mar 22 '19 at 18:50
  • 1
    besides the horrible misuse of `Start-Process` [*grin*], your answer is here ... How do I include a locally defined function when using PowerShell's Invoke-Command for remoting? - Stack Overflow — https://stackoverflow.com/questions/11367367/how-do-i-include-a-locally-defined-function-when-using-powershells-invoke-comma – Lee_Dailey Mar 22 '19 at 20:05
  • Possible duplicate of [How do I include a locally defined function when using PowerShell's Invoke-Command for remoting?](https://stackoverflow.com/questions/11367367/how-do-i-include-a-locally-defined-function-when-using-powershells-invoke-comma) – Lee_Dailey Mar 22 '19 at 20:42

2 Answers2

0

try including your custom function definition inside the scriptblock, sort of like this

invoke-command -scriptblock {
    param (
        your params
    )

    begin {
        function write-log (etc) {
            the function code
        }
    }

    process {
        your main scriptblock stuff
    }

    end {}
}
Anthony Stringer
  • 1,981
  • 1
  • 10
  • 15
0

I did try to use ${function:Write-Log} but it didn't work.

${function:Write-Log} uses namespace variable notation to get the Write-Log function's body as a script block.

Since you're using remoting (you're passing a -ComputerName argument to Invoke-Command), you'll either have to pass that script block as an argument to the remotely executing script block, or - more conveniently - utilize the $using: scope (PSv3+) to make a local variable value available remotely.

Unfortunately, $using: cannot be combined with namespace variable notation, as of Windows PowerShell v5.1 / PowerShell Core 6.2.0 - see this GitHub issue.

Therefore, with the bug present, referencing your script block as ${using:function:Write-Log} does not work directly, but you can use an intermediate variable, and then use that to adapt this excellent answer, using a simplified scenario:

Note: To run this code, which simulates remoting by connecting to the same machine, make sure that remoting is enabled on your machine, and invoke the code as admin.

function Write-Log { 
  param([string] $ComputerList, $message, $level="INFO")      
  # For testing simply echo the arguments
  "$ComputerList, $message, $level"
}

# Recreate the function's full source code as a string.
$funcDef = "function Write-Log { ${function:Write-Log} }"

Invoke-Command -Computer . -ScriptBlock {

  # Define the Write-Log function using Invoke-Expression
  # Note: This is a rare case where use of Invoke-Expression is
  #       justified; generally, it is to be AVOIDED - see
  #       https://blogs.msdn.microsoft.com/powershell/2011/06/03/invoke-expression-considered-harmful/
  Invoke-Expression $using:funcDef

  # Now you can call it as you would locally.
  Write-Log 'ws1 ws2' testing DEBUG

}

The above should return ws1 ws2, testing, DEBUG.


Of course, if the Write-Log source code is:

  • readily available to you, and
  • you don't mind duplicating it inside the script block,

you can just make the function definition part of the remotely executing script block:

Invoke-Command -Computer . -ScriptBlock {

  # Embedded copy of Write-Log.
  function Write-Log { 
    param([string] $ComputerList, $message, $level="INFO")      
    # For testing simply echo the arguments
    "$ComputerList, $message, $level"
  }

  # Now you can call it as you would locally.
  Write-Log 'ws1 ws2' testing DEBUG

}
mklement0
  • 382,024
  • 64
  • 607
  • 775