Execute Remote Script

First what I am trying to do is have a message box on a remote computer that is activated by my host computer and perform various commands for whatever they click. I then want it to send either a 1 or a 0 back to me so I can log what they clicked.

I know that I cannot use the Invoke-Command -computer $computer -FilePath $myScript because windows doesn’t let you send objects through this command (not sure why but I’ve been doing my research)

My question then becomes, is there a way to activate the script that I have stored on the remote computer so I am not sending an object through the invoke command and then get a return value from that? Any links, advice, help is greatly appreciated!

Hey Zac,

If the program that displays a message box is a binary, then yes, you’ll be pretty much restricted to a generic copy operation to the system. PowerShell’s native command for that is of course Copy-Item
All types descend from system.object, so it’s not really true that you can’t send over objects via a remote session, it’s more that you are restricted to types that are strings, or that can be cast from a string type object.
If your message box program is using PowerShell code, then unless your code is ridiculously long, you access this remotely via a generic string, and write out to a ps1 file on the target system, or even executed it directly as a scriptblock.
For interactive operation, you can use Win32_ScheduledJob with the interactive option enabled and then trigger execution of it for it to be visible to the user.
Returning data back is dependent on the message box code actually returning data. Provided it does that, you can output the result in your scriptblock, which will return the output to the local session. e.g.

$result = Invoke-Command -ComputerName MyRemoteComputer -Scriptblock {
$x = Get-ChildItem
$x
}

This might sound confusing as heck. Let me know if you need some more info and I can see about getting a sample script drawn up to show you.

Hope this helps. :slight_smile:

I’m not sure what you mean by binary message box, but I have a powershell script that creates a message box and performs a shutdown either after a timeout, or a user yes and aborts on no. I want to be able to log things about which answer is chosen.
I’ll have to try the entire code through scriptblock method when I am back at work

Can you explain a little more about the Scheduled Job option? Is this like I set a script to run through task scheduler on my target systems that can only be invoked by a certain user?

Hi Zac,

You can send objects to remote machines using Invoke-Command with the -FilePath and -ArgumentList parameters.

Please find a simple example below:

# Remote.ps1
Param (

    [Parameter(Mandatory=$true)]
    [Hashtable]
    $Hashtable,

    [String[]]
    $Exclude
)

foreach ($Item in $Hashtable.GetEnumerator()) {

    if ($Exclude -notcontains $Item.Key) {

        Write-Output -InputObject $Item.Key
    }
}
# Local script running on an Admin workstations or Jump server
$Travel = @{
    'Monday' = 'Dallas'
    'Tuesday' = 'Denver'
    'Wednesday' = 'Seattle'
}
Invoke-Command -ComputerName localhost -FilePath "C:\Remote.ps1" -ArgumentList $Travel, 'Tuesday'

The output will be:

Monday
Wednesday

Regards
Daniel

I'm not sure what you mean by binary message box, but I have a powershell script that creates a message box and performs a shutdown either after a timeout, or a user yes and aborts on no. I want to be able to log things about which answer is chosen. I'll have to try the entire code through scriptblock method when I am back at work

Can you explain a little more about the Scheduled Job option? Is this like I set a script to run through task scheduler on my target systems that can only be invoked by a certain user?

Hey Zac, I wasn’t sure if your program with the message box was running via a powershell script or a binary EXE type file which would have made it a bit trickier.

Returning data from a form is relatively straight forward. The main thing you’re going to need to do in your script is to declare a scope variable of type $script or $global before loading the form. That way the form will have access to the variable and can populate it as required. Here’s an example of some of the code you could use. The form i created features one textbox ($textbox1), and a button to exit ($button1). I’ll leave out the actual generation of the code and just show you what happens with the events.

#Top of the script
$global:Output =''
.
.
.
.
#When the button is clicked (which exits the form), store the contents of the textbox into the global variable $global:output
$button1_Click={
		#TODO: Place custom script here
	$global:output = $textbox1.Text
      exitme
}

#This is to allow the form to close properly.
	function exitme{ 
	  $mainform.Close()
	}
	    

Because a scheduled job allows you to run an interactive session, this is a way for you to spark off it running. You can choose it to be executed on the target box after you’ve run the script on your side, or at a scheduled time. MSDN has more details on the class that might help. - https://msdn.microsoft.com/en-us/library/aa394399(v=vs.85).aspx

I currently have this code to be run on the remote computer

{#RemoteScript.ps1
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

$global:Output = ' '

#region objForm Dialog Box opening
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Data Entry Form"
$objForm.ControlBox = $false
$objForm.Size = New-Object System.Drawing.Size(300, 200)
$objForm.StartPosition = "CenterScreen"

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10, 20)
$objLabel.Size = New-Object System.Drawing.Size(280, 60)
$objLabel.Text = "The Lab is being shut down. You have 30s to abort before your station is automaticcaly shut down.`n`nDo you wish to proceed with termination?"
$objForm.Controls.Add($objLabel)



$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75, 120)
$OKButton.Size = New-Object System.Drawing.Size(75, 23)
$OKButton.Text = "Yes"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::Yes
$OKButton.Add_Click({$objForm.Close() })
$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150, 120)
$CancelButton.Size = New-Object System.Drawing.Size(75, 23)
$CancelButton.Text = "No"
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::No
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)
#endregion

#refresh dialog

$refresh = {
	$timer.Stop()
	
	if ($useOutput -eq $null)
	{
		#shut down
		$objForm.Close()
		$global:Output = 10
		return $global:Output
		[System.Windows.Forms.MessageBox]::Show("SHUT IT DOWN")
	}
	else
	{
		continue
	}
}
#timer
$timer = New-Object System.Windows.Forms.Timer #$objForm
$timer.Interval = 1000*10  # once per 10 second
$timer.Add_Tick($refresh)

$timer.Start()
$useOutput = $objForm.ShowDialog()
##Script should pause here
# When script pauses, there is a timer still running, when it hits interval seconds it calls refresh
# When refresh is called, it stops the timer, closes the dialog, and checks the output of the dialog box
# Dialog box should return null if nothing was clicked, so it will shut down the system
# If a user clicks a button before the timer ends it should continue the script and check yes or no


$timer.Stop()
#$useOutput = [System.Windows.Forms.MessageBox]::Show("The CLiCC Lab is being shut down. You have 30s to abort before your station is automaticcaly shut down.`n Do you wish to proceed with termination?", "WARNING", 'YesNo', 'Exclamation')


if ($useOutput -eq [System.Windows.Forms.DialogResult]::Yes)
{
	$global:Output= 0
	return $global:Output
	[System.Windows.Forms.MessageBox]::Show("Termination activated. Good bye.")
}
elseif ($useOutput -eq [System.Windows.Forms.DialogResult]::No)
{
	$global:Output = 1
	return $global:Output
	[System.Windows.Forms.MessageBox]::Show("Aborting shutdown")
}

}

and then I’m assuming this will send $global:Output back to my locally run script as $errRTN shown below

{#LocalMainScript.ps1
$remScript = "C:\ShutdownHostFile.ps1"
$command = "powershell.exe -File $remScript -noexit"
$startTime = Get-Date -UFormat g
#might have to use Get-Date -UFormat g
$startTime = ConvertFromDateTime($startTime)
$sJob = [wmiclass]"\\Remote-Computer\root\cimv2:win32_scheduledjob"
$errRTN = $sJob.Create($command, $startTime, $false, $null, $null, $true) #($command,$startTime,$repeat,$daysOfWeek,$daysOfMonth,$interactWithDesktop)
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")


[System.Windows.Forms.MessageBox]::Show("Returned $errRTN") #will change this to behave differently based on value returned
}

I’m afraid $errRTN will return a 0 if the script is ran properly, or works and 1 if it doesn’t and not be dependent on user input. Do I have to send it through the $command part as an argument?

Thanks