A script file to Scotty: Beam me up with PowerShell Remoting

by Klaus Graefensteiner 8. April 2011 11:11

Introduction

If you do some experimenting with PowerShell Remoting you might at one point ask yourself the following question: How do I get the my script file to the remote machine and kick it off via a PowerShell Remoting command. Of course you could copy it to a share on the remote computer, but that would require that somebody actually needs to create that share in advance. You could also consider the PModem approach that Oisin Grehan described in his blog post PowerShell 2.0 – Introducing the PModem File Transfer Protocol. This approach again requires that a script or module needs to be installed on the target machine.

Transporter

Figure 1: A Star Trek transporter

The Transporter

Like I said. I played with several approaches and ended up with a fiendishly extreme solution. PowerShell Remoting lets you specify a Scriptblock that gets transported to the target machine. What if I would just dynamically generate this Scriptblock and have it contain the contents of a script file as a string. All I need on the target machine is to unload this cargo into a script file and call it. Here are the steps in detail:

  1. Read the file into a variable
  2. Encode the characters that the PowerShell compiler would not allow in a script block .
  3. Dynamically generate a code snippet with this encoded string.
  4. In this code snippet wrap the encoded cargo string into a few commands that would decode the string at the other end, save it as a file and execute it on the target machine.

scotty

Figure 2: Scotty is getting the plumbing right. Let’s hope for him and his 400 co-workers.

The Script

The transporter script

Set-StrictMode -Version "Latest"
$DebugPreference = "Continue"

$ScriptFileFolder = Split-Path -Path $($MyInvocation.MyCommand.Definition) -Parent
Write-Debug $ScriptFileFolder


#Use this function to create a value for the password in the configuration file
function Create-SecretPasswordFile([string] $PasswordFilePath)
{
    $Password = Read-Host "Enter your password" -AsSecureString
    $EncryptedPassword = ConvertFrom-SecureString $Password
    $EncryptedPassword | Out-File -FilePath $PasswordFilePath
}

function Load-Configuration([string] $ConfigurationFileName)
{   
    $ConfigFilePath = Join-Path -Path $ScriptFileFolder -ChildPath $ConfigurationFileName
    Write-Debug $ConfigFilePath
    if(Test-Path -Path $ConfigFilePath)
    {
      . $ConfigFilePath        
    }
    else
    {
        Throw "Configuration file `"$ConfigFilePath`" not found!"
    }
}


function Create-PSRemotingSession()
{
    $SecurePassWord = ConvertTo-SecureString $Config.PSRemotingCredentialPassword #Key is only usable by the domain user who created it
    $DomainUser = "{0}\{1}" -f $Config.PSRemotingCredentialDomain, $Config.PSRemotingCredentialUserName
    Write-Debug $DomainUser
    $Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $DomainUser, $SecurePassWord
    $Session = New-PSSession -ComputerName $Config.RemoteComputerName -credential $Cred
    return $Session
}

function Encode-String([string] $s)
{
    $s = $s.Replace('@', '[at]')
    $s = $s.Replace("'", '[tick]')
    return $s
}


function Copy-FileRemote([String] $LocalFilePathHere, [String] $LocalFilePathOnRemote="C:\Testing\Target.txt")
{
    
    $ContentArray = Get-Content -Path $LocalFilePathHere -Encoding "UTF8"
        
    foreach($Content in $ContentArray)
    {
        $CopyScriptStringOld = @'    
    Add-Content -Value '{0}' -Path "{1}" -Encoding "UTF8" -Force
'@    
        $CopyScriptString = @'    
    $Content = '{0}'
    $Content = $Content.Replace('[at]', '@')
    $Content = $Content.Replace('[tick]', "'")
    
    Add-Content -Value $Content -Path "{1}" -Encoding "UTF8" -Force
'@ 
        Write-Debug $Content
        
        $EncondedContent = Encode-String -s $Content
    
        $CopyScriptString = $CopyScriptString -f $EncondedContent, $LocalFilePathOnRemote
        $CopyScript = [scriptblock]::Create($CopyScriptString)
    
        Write-Debug $CopyScriptString
    
        $job = Invoke-Command -Session $Session -Scriptblock $CopyScript -AsJob
        $Null = Wait-Job -Job $Job
    }
}

$Config = Load-Configuration -ConfigurationFileName "TransporterConfig.ps1"
$Session = Create-PSRemotingSession
Copy-FileRemote -LocalFilePathHere "C:\Users\KlausG\Desktop\Transporter\BeamMeUp-Scotty.ps1" -LocalFilePathOnRemote "C:\Users\Administrator\Desktop\Transporter\BeamMeUp-Scotty.ps1"
Copy-FileRemote -LocalFilePathHere "C:\Users\KlausG\Desktop\Transporter\Hello-World.ps1" -LocalFilePathOnRemote "C:\Users\Administrator\Desktop\Transporter\Hello-World.ps1"
$job = Invoke-Command -Session $Session -Scriptblock {. C:\Users\Administrator\Desktop\Transporter\Hello-World.ps1} -AsJob
$Null = Wait-Job -Job $Job

Remove-PSSession -Session $Session

The transporter configuration

Write-Debug "Loading Config File"
$Config = @{}
$Config.RemoteComputerName = "WIN-VE0uioD41"
$Config.PSRemotingCredentialDomain="Village"
$Config.PSRemotingCredentialUserName="Sepp"
#The password key is only usable by the domain user who created it. 
#Use the Create-SecretPasswordFile function to create one for your login
$Config.PSRemotingCredentialPassword="0000000000000999999999...eeeeeeee"
return $Config

The hello world script

Set-content -Value "Hello World" -Path "C:\Users\Administrator\Desktop\Transporter\Hello-World.txt"

Power

A few things to consider before you push the power button to avoid the overloading of the circuits:

  1. Enable PS Remoting on both machines.
  2. Enable script execution on both machines.
  3. Make sure that both machines are on the same domain or, using winrm.cmd, create a trust relationship between the two machines.

These two blog posts describe the extra steps for running PowerShell Remoting in a workgroup setting:

http://blogs.msdn.com/b/wmi/archive/2009/07/24/powershell-remoting-between-two-workgroup-machines.aspx

http://rkeithhill.wordpress.com/2009/05/02/powershell-v2-remoting-on-workgroup-joined-computers-%E2%80%93-yes-it-can-be-done/

Before

Figure 3: Before the transport

After

Figure 4: After the transport and the execution of hello-world.ps1

Download

The script files can be downloaded here: Transporter.zip

Ausblick

With great PowerShell comes great responsibility. May the Force be with you!

Tags: , , , , ,

PowerShell | Tips & Tricks

About Klaus Graefensteiner

I like the programming of machines.

Add to Google Reader or Homepage

LinkedIn FacebookTwitter View Klaus Graefensteiner's profile on Technorati
Klaus Graefensteiner

Klaus Graefensteiner
works as Developer In Test and is founder of the PowerShell Unit Testing Framework PSUnit. More...

Open Source Projects

PSUnit is a Unit Testing framwork for PowerShell. It is designed for simplicity and hosted by Codeplex.
BlogShell is The tool for lazy developers who like to automate the composition of blog content during the writing of a blog post. It is hosted by CodePlex.

Administration

About

Powered by:
BlogEngine.Net
Version: 1.6.1.0

License:
Creative Commons License

Copyright:
© Copyright 2013, Klaus Graefensteiner.

Disclaimer:
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Theme design:
This blog theme was designed and is copyrighted 2013 by Klaus Graefensteiner

Rendertime:
Page rendered at 5/21/2013 3:28:39 AM (PST Pacific Standard Time UTC DST -7)