Code design when dealing with remoting

I have a question for you that is not about specific code or problem but rather about code design.
Specifically about code which must work for local computer as well as remote computer.

Here is an example template code:

function Invoke-Process
{
	Param (
        [Parameter(Mandatory = $true)]
		[string] $Path,

        [Parameter(ParameterSetName = "Domain")]
		[string] $Domain,

        [Parameter(ParameterSetName = "Domain")]
        [PSCredential] $Credential,

        [Parameter(ParameterSetName = "Session")]
        [System.Management.Automation.Runspaces.PSSession] $Session,
        )

    # Initialize session to either localhost or remote host
    $SessionParams = @{}

	if ($PSCmdlet.ParameterSetName -eq "Session")
	{
		$SessionParams.Session = $Session
	}
	else
	{
        # ensures using loopback for localhost
		if ($Domain -ne [System.Environment]::MachineName)
		{
			$SessionParams.ComputerName = $Domain
			if ($Credential)
			{
				$SessionParams.Credential = $Credential
			}
		}
        # else $SessionParams is blank which means Invoke-Commands runs against local computer
	}

    # This will work in all scenarios, regardless if local host or remote
	Invoke-Command @SessionParams -ScriptBlock {
	     # Here goes lengthy code only once...
    }
}

A problem I have with this template code is that when this function runs on localhost it will use loopback interface, which means additional performance overhead.

For example if the “lengthy code” portion is put outside Invoke-Command for localhost then no loopback would be used, however this would result in code bloat:

if ($localhost)
{
    # Not using Invoke-Command
 
    # here goes lengthy code
}
else
{
    Invoke-Command @SessionParams -ScriptBlock {
        # Here goes lengthy code yet again...
    }
}

Surely there must be a clean solution which would guarantee all 3 conditions:

  1. No code bloat
  2. No Invoke-Command for localhost
  3. function works for both localhost and remote host.

What are your suggestions, how would you design such function?

EDIT:
I didn’t say but there are some other potential issues with single Invoke-Command such as not being able to use $using:Variable since using is valid only in remote contexts, it would stop working when the function runs locally.

FWIW, I maintain a very large script that is designed to run against local and remote, and I chose your second approach, but with a small twist. I only use that localhost check to create a CimSession, then reference that CimSession in the remainder of the script.

Thanks for posting this. If someone has a great solution, I am all ears.

1 Like

I’m glad to hear I’m not alone with this problem :slightly_smiling_face:

I’ve been playing around and come to one possible option, which is using scriptblock to define function and then conditionally run the scriptblock depending on localhost or not.

in my previous example I define scriptblock within function:

	[scriptblock] $Code = {
		param (
			$Path,
		)

       # here goes lengthy code in singe place
     }

Then after this run scriptblock depending on target:

if ($localhost)
{
    # run code locally
    & $Code $Path
}
else
{
    # run code remotely
    Invoke-Command @SessionParams -ScriptBlock $Code -ArgumentList $Path
}

This so far looks great but there is one problem…

My “lengthy code” in practice consists of some 500 lines, and often times I use Write-Error within that code, if an error happens within scriptblock (ex. Write-Error “whatever”) then the entire scriptblock is printed to console in addition to the error.
Not only the console is spammed but also it’s harder to debug the problem if you run multiple of functions designed is this way.

This happens only with & $Code $Path when executing scriptblock directly, it doesn’t happen with Invoke-Command

I don’t know why this is so and what could be done about that, maybe I should post a minimal reproducible example?