Search code examples
powershellremote-accessinvoke-commandsplat

Local or remote execution of powershell script with generic parameters


In a development team, I would like to have the same test scripts to be executed locally by a developper or remotely by our test platform.

Here is what I would like to use as premises for each script

# Test local/remote execution by reading C:\ directory
param(
    [switch] $verbose,
    [switch] $remote,
    [string] $ip,
    [string] $user,
    [string] $password
    #Add here script specific parameters
)

Write-Host "Command invokation incoming parameter count : " $psboundparameters.count

if ($remote) {
    $Params = @{}
    $RemoteParams = @{}
    $pass = ConvertTo-SecureString -String $password -AsPlainText -Force 

    $Params.Credential = new-object -TypeName System.management.automation.PSCredential -argumentlist $user, $pass
    $Params.ComputerName = $ip
    $Params.FilePath = $MyInvocation.MyCommand.Name
    $null = $psboundparameters.Remove('remote')
    $null = $psboundparameters.Remove('ip')
    $null = $psboundparameters.Remove('user')
    $null = $psboundparameters.Remove('password')

    foreach($psbp in $PSBoundParameters.GetEnumerator())
    {
        $RemoteParams.$($psbp.Key)=$psbp.Value
    }
    Write-Host $RemoteParams
    Invoke-Command @Params @Using:RemoteParams
    Exit 
}

Write-Host "Command execution incoming parameters count : "    $psboundparameters.count

# Here goes the test 
Get-ChildItem C:\

However, when I execute this, I got the following error:

Invoke-Command : A positional parameter cannot be found that accepts argument '$null'.

It seems that @Using:RemoteParams is not the correct way of doing this, but I'm quite lost here. Thanks in advance


Solution

  • Here's my take on the problem of being able to do both local and remote execution using named parameters:

    $IP = '192.168.0.1'
    $User = 'Test User'
    $Password = 'P@ssW0rd!' 
    
    $params = @{
    IP = $IP
    User = $User
    Password = $Password
    }
    
    $command = 'new-something'
    
    $ScriptBlock = [Scriptblock]::Create("$command $(&{$args} @Params)")
    

    Start with a hash table of parameters, using local varibles, then use this:

    [Scriptblock]::Create("$command $(&{$args} @Params)")
    

    to create a script block of the command, with the parameters inline and the values already expanded. Now that script block is ready to be run locally (either by invocation with & or dot-sourcing), or remotely using Invoke-Command.

    $ScriptBlock
    new-something -IP: 192.168.0.1 -User: Test User -Password: P@ssW0rd!
    

    No scoping with $Using: or -argumentlist required.

    Edit: Here's an example using a script rather than a single command:

    $path = 'c:\windows'
    $filter = '*.xml'
    
    $Params = 
    @{
       Path = $path
       Filter = $filter
      }
    
    $command = @'
    {
      Param (
        [String]$path,
        [String]$Filter
       )
    
     Get-childitem -Path $path -Filter $filter
    }
    '@
    
    $ScriptBlock = [Scriptblock]::Create(".$command $(&{$args} @Params)")
    

    To run it locally:

     Invoke-Command $ScriptBlock
    

    or just:

     .$ScriptBlock
    

    To run it remotely:

     Invoke-Command -Scriptblock $ScriptBlock -ComputerName Server1