How can I run a PowerShell function remotely?

Solution 1:

Firstly to return information don't use Write-Host (this is true always, unless you really only want to both have colours and only operate interactively locally).

Make the output the return value of the function:

function getcontentfile 
{
    [CmdletBinding()]
    param($hostname)
    $info = Get-Content "C:\fileinfo.xml"
    $info
}

Secondly: enable PSH remoting on the target systems: see the help for Enable-PSRemoting.

Thirdly: run the command remotely:

Invoke-Command -computer comp1,comp2 -ScriptBlock { Get-Content "C:\fileinfo.xml"  }

This will return the contents of the file on the two computers, to separate the results adding -AsJob will return job objects which can then be queried separately with the Job cmdlets (see gcm -noun job for a list, note Receive-Job to get the results of a job).

Solution 2:

You can run a local loaded function on a remote Machine:

Invoke-Command -ComputerName Comp1 -cred $cred -ScriptBlock ${function:get-contentfile } -argumentlist "ParameterA", "ParameterB"

Solution 3:

Your first option is to enable Powershell 2.0 Remoting.

Personally, I wasn't really interesting in remoting although it is powerful, so I wrote a script to use WMI, create a process with cmd.exe and then pipe the stdout and stderr to a log file which you can then read.

The script leaves its log file on the remote computer, so you could simply: get-content \\remotecomputer\c$\remoteExec.log to read it.

<#
.SYNOPSIS
    Remotely executes a command and logs the stdout and stderr to a file on the
    remote computer.
.DESCRIPTION
    This script accepts three parameters (one optional) and executes a program on
    a remote computer.  It will verify connectivity and optionally (verifyPath) the
    existence of the program to be executed.  If either verifications fail, it will
    not attempt to create the process on the remote computer.
.EXAMPLE
    .\remoteExec.ps1 -program "dir" -args "c:\" -computerName "SEANC"
.EXAMPLE
    .\remoteExec "C:\Windows\SysWOW64\msiexec.exe" "/i c:\a.msi /passive /log c:\a-install.log" SEANC C:\Windows\Temp\remote.log -verifyPath
.PARAMETER computerName
    The name of the computer on which to create the process.
.PARAMETER program
    The command to run on the remote computer.
.PARAMETER args
    The command arguments.
.PARAMETER log
    The file to which the stderr and stdout generated by the command will be redirected.
    This is a local path on the remote computer.
.PARAMETER verifyPath
    Switch to enforce path verification.
#>
param(
    [parameter(Mandatory=$true)] [string]$program,
    [parameter(Mandatory=$false)][string]$args = "",
    [parameter(Mandatory=$true)] [string]$computerName,
    [parameter(Mandatory=$false)][string]$log = "C:\remoteExec.log",
    [parameter(Mandatory=$false)][switch]$verifyPath = $false
)

if (-not (Test-Connection $computerName -Quiet -Count 1))
{
    return Write-Error "Unable to connect to $computerName."
}

if ($verifyPath -and (-not (Test-Path \\$computerName\$($program.replace(":","$")) -PathType Leaf))) {
    return Write-Error "Path $program does not exist on $computerName."
}

try {
    $remoteWmiProcess = [wmiclass]"\\$computerName\root\cimv2:win32_process"
    $remoteProcess = $remoteWmiProcess.create(
        "cmd.exe /c `"$program $args > $log 2>&1`""
    )
} catch {
    return Write-Error ("Unable to create process through WMI.");
}

if ($remoteProcess.returnValue -ne 0) {
    return Write-Error ("FAILED on $computerName with return code: " + $remoteProcess.returnValue)
} else {
    return ("Successful trigger on $computerName; returned: " + $remoteProcess.returnValue)
}

EDIT: In this example, the script is called remoteExec.ps1 and I use it to create a remote powershell process and run a command (what the asker is attempting to do):

.\remoteExec.ps1 -program "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -verifyPath -computerName "computer1" -args "-command Get-ChildItem C:\"

I could then read the log with:

Get-Content \\computer1\C$\remoteExec.log