Remotely run script but pass in the script to a executing script as variable

Hi,

I have a strange implementation that i am having issues with.
The process i have in place is a host script that accepts variables of “RemotePCName” (remoteNodeto run script on), “ScriptToRun” (txt of script to execute), "ParamtersForScript (parameters of the script).

The idea being that in a UI that is run under a particular domain account that has remote execution access and this will allow anyone to save a function script with parameters and let them add parameter key/pair values and then execute that script on any remote machine through a controlled user account that has the correct access.

The script executes basic functions with parameters and works well, the problem is that once it is a complex script with double quotes and unexpanded variable values then it fails.
Example script variable text below.

The problem is the script is passed as a var the is now split by the quotes therefore corrupting the var input.

I know this is not a great way to accomplish this tasks but its what i am trying .
Any help or suggestions appreciated but the main requirement is that the script must be passed in as a var and then invoked as a scriptblock on the remote machine.

Thanks in advance.

Function Get-WebLogs
{
	Param
	(
		[Alias('Computer','ComputerName','HostName')]
		[Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=1)]
		[string]$Name,
		[Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=2)]
		[string]$RootDir,
		[Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=2)]
		[string]$WebLogPath  
	)

	try
	{
        $TargetDir = "$RootDir\$Name\"
        $TargetDir
        if(!(Test-Path -Path $TargetDir )){
            New-Item -ItemType directory -Path $TargetDir
        }
        Copy-Item -Path "\\$Name\$WebLogPath\*.txt" -Destination $TargetDir

	}
	catch
	{
	    Write-error $_.Exception.Message
    }
	

}

I’m sorry your explanation of the problem is confusing and it seems that you didn’t post your main script with the problem. To me it looks like the posted function Get-WebLogs is what you want to execute remotely and not what is actually been invoked through the UI.

I’m guessing you’re looking for something like below:

param
(
    [String]
    $RemotePCName,

    [String]
    $ScriptToRun,

    [String[]]
    $ParametersForScript
)

$Script = Get-Content -Path $ScriptToRun -Raw

Invoke-Command -ComputerName $RemotePCName -ScriptBlock {

    $ScriptBlock = [ScriptBlock]::Create($Using:$Script)
    $ScriptBlock.Invoke($Using:ParametersForScript)
}

Hi Daniel,

Sorry for the confusing post, I did not explain the problem very well but you have sifted through my bad explanation and come to the crux of the problem.

Also your solution is very similar to what i had created, which i will post now (apologies for neglecting it in the original post).

The call to this script (located at the bottom ) is only to test, as the actual call is C# code and the script call line is built in the code base and appended in a similar format but the variable “CustomScript” is one long string of the Get-Weblogs script including all “$” and double quotes etc.
It is the unexpanded variables and numerous double quotes that are causing the issues

Again i am unsure that i have explained it well enough but your suggestion solution may have given me the idea for a resolution.

Thank you for your reply, much appreciated.

#Helper Functions Start
Function GetFunctionName {
    param(
        $scriptFile
        #,
        #$functionName
    )

    $code =  $scriptFile
    $tokens = [System.Management.Automation.PSParser]::Tokenize($code, [ref]$null)

    $waitForFuncName = $false
    $funcFound = $false
    $level = 0
    $funcName =''

    foreach($t in $tokens) {
        switch($t.Type) {
            Keyword {
                if ($t.Content -eq 'function') {
                    $waitForFuncName = $true
                }
            }
            CommandArgument {
                if ($waitForFuncName) {
                    $waitForFuncName = $false
                    $funcName = $t.Content
                    #$funcFound = $t.Content -eq $functionName
                }
            }
            GroupStart {
                ++$level
            }
            GroupEnd {
                --$level
                if ($funcFound -and $level -eq 0 -and $t.Content -eq '}') {
                    return $t.StartLine
                }
            }
        }
    }
    return $funcName
}
#Helper Functions End

#Main Function

Function Set-CustomAdHoc
{
	Param
	(
		[Alias('Computer','ComputerName','HostName')]
		[Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=1)]
		[string]$Name,
        [Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=2)]
		[string]$CustomScript ,
        [Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Position=2)]
		[string[]]$CustomScriptParameters 
	)



	try
	{
        $InternalFunctionName = GetFunctionName $CustomScript
        $ScriptParams=''
        $ScriptParamNamesValues=''
        foreach ($element in $CustomScriptParameters) 
        {
	        $paramArraySplit  = $element.Split("=")
            $ScriptParams += "$" + $paramArraySplit[0] + ","
            $ScriptParamNamesValues += "-" +$paramArraySplit[0] + " " + $paramArraySplit[1] + " "
        }

        
        $ScriptParams  = $ScriptParams -replace ".$"
        $ScriptBlockScriptParams = "Param($ScriptParams)"

        $internal = " $CustomScript $InternalFunctionName $ScriptParamNamesValues" 
        $scriptBlock = [scriptblock]::Create($internal)

        $result.List=Invoke-Command -ComputerName $Name -ScriptBlock $scriptBlock
	}
	catch
	{
		$_.Exception.Message
	}

}


$scriptPath = "c:\PathToGet-WebLogs.txt"
$test =[IO.File]::ReadAllText($scriptPath)


Set-CustomAdHoc -Name Testapp01 -CustomScript $test  -CustomScriptParameters @('Name="Testapp01"','RootDir="\\Server\VMSharedFolder\Debug"','WebLogPath="c$\dsc\Logs"') 

Thanks for posting the additional code. Would you be able to post a code snippet from your UI where you’re invoking PowerShell? I have a feeling that your approach is more complicated that necessary. I will dedicate some time tomorrow and fire up Visual Studio to test an idea I have had after seeing the additional code. I hope that is okay for you.

Hi Daniel,

That would very much appreciated, the basic calling code is below.
i am basically building the last script calling line and appending it.

var parameters = new MethodParameters
{
new Parameter {ParameterName = Constants.Name, ParameterValue = ipAddress},
new Parameter {ParameterName = “CustomScript”, ParameterValue = _customScript},
new Parameter {ParameterName = “CustomScriptParameters”, ParameterValue = _customScriptParameters},
}

public InvokeRemoteCommand(string scriptValue, MethodParameters parameters)
{
  RunSpace = RunspaceFactory.CreateRunspace();
  RunSpace.Open();
  Pipeline = RunSpace.CreatePipeline();
  Powershell = PowerShell.Create();

  foreach (Parameter param in parameters)
  {
    scriptValue += " -" + param.ParameterName + " \"" + param.ParameterValue.RemoveLineEndings() + "\" ";
  }
  Powershell.AddScript(scriptValue);

  Log.Info("Command : " + scriptValue);
  Powershell.Runspace = RunSpace;
}

Please find below what I’ve managed to come up with in C# so far. Unfortunately, PowerShell Remoting is broken on my machine so I haven’t had the chance to test it end-to-end but I think you’ll get the idea. If I can make the time in the next few days I’ll try to test it against another box and update the Gist if necessary.