James O'Neill's Blog

August 23, 2011

Enhancing the NetCmdlets

Filed under: Powershell — jamesone111 @ 1:11 pm

At PowerShell Deep dive conference in April /n software gave away USB keys with a full set of “PowerShell inside” software including their NetCmdlets. It turns out that this is 30 day evaluation software, and initially this put me off using them; Joel wrote about an alternative saying “It’s also possible to do this using the NetCmdlets from /n and I’m constantly surprised at how unwilling people are to pay for them.”  Part of the problem is you get nearly 100 cmdlets, and if you want only half a dozen if feels like most of the cost is wasted. After checking a free library and finding it flakey I dug out the USB key, figuring if they worked, or even mostly worked they’d be worth the cost.

I only need to do a couple of things – use SSH to send commands to the Linux server which hosts my application using (for which there are 3 commands: Connect-SSH, Disconnect-SSH and Invoke-SSH) and move files both ways with sftp (which has Connect-SFTP, Disconnect-SFTP, Get-SFTP, Remove-SFTP, Rename-SFTP and Send-SFTP) .  I wanted to be able to invoke the commands against my server without re-making connections each time , and that meant some simple “wrapper” commands to get things how I wanted them.

Connect-Remote Sets up a Remote Session (saves the session object as a global variable)
All the following functions can be passed a remote session but use the global variable as a default
Get-RemoteItem Get properties one or more remote items (Like Get-Item / Get-ChildItem) – alias rDir
Copy-RemoteItem Copies a remote item to the local computer (like Copy-Item ) –alias rCopy
Copy-LocalItem Copies a local item to the remote computer
Remove-RemoteItem Deletes an item on the remote computer  (like Remove-Item)
Invoke-RemoteCommand Runs a command on the remote computer. (Like Invoke-Command)

The two connect- netcmdlets return different types of object which contain the server, credential and various other connection parameters. I can set them up like this

$credential = $Host.ui.PromptForCredential("Login",
   "Enter Your Details for $server","$env:userName","")
$Global:ssh = Connect-SSH -Server $server -Credential $credentialForce

I found downloading causes the sftpConnection object to “stick” to the path of downloaded file. Get-, Remove-, Rename- and Send- all allow the server and credential to be passed, so I can pass the SSHConnection object into my SFTP wrappers and use its server and credential properties – even though it is the “wrong” connection type – being for SSH command-lines rather than SFTP file access. I wrote a Connect-Remote function which also stores the connections so I can switch between them more easily. It looks like this:

$Global:AllSSHConnections = @{}
Function Connect-Remote { 
Param ([String]$server = $global:server,
      
[String]$UserName =$env:userName , 
       [Switch]$force
     
)
      if   ($Global:AllSSHConnections[$server] -and -not $force) {
             
$Global:ssh = $Global:AllSSHConnections[$server]} 
      else { $credential = $Host.ui.PromptForCredential("Login","Enter Your Details for $server",$username,"")
      # Connect-ssh takes errorAction parameter but it doesn't work. So use try-catch 
      try {$Global:ssh = Connect-SSH -Server $server -Credential $credential -Force -ErrorVariable SSHErr }
      catch {continue}
      If ($?) {$remotehost = (invoke-RemoteCommand -Connection $ssh -CmdLine "hostname")[0] -replace "\W*$",""
               Add-Member -InputObject $ssh -MemberType "NoteProperty" -Name "HostName" -Value $remoteHost
               $Global:AllSSHConnections[$server] = $ssh
               $ssh | out-default 
      }
}

Storing the connection simplifies using the other cmdlets. I have a little more error checking in the real version of the script so I can tell the user if there was simply a password error or something more fundamental at fault.

The first task I wanted to perform was to get a file listing via sftp. I have a couple of easily-solved gripes with the netcmdlets: I’ve already mentioned the sticking sftp connection, the other is that the EntryInfo object which is returned when getting a file listing doesn’t contain the fully qualified path, just the file name – so I sort the entries into order, and add properties for the directory and fully qualified path and used New-TableFormat  to give me the start of a formatting XML file to display the files nicely . The function also takes the path, and a connection object – which defaults to the connection I set up before.

Function Get-RemoteItem {
    param( [Parameter(ValueFromPipeLine=$true)]
          
[String]$path= "/*",
           $Connection  = $Global:ssh 
         )
process {
           if ($Connection.server -and $Connection.Credential ) {
                  $Directory = $path -replace "/[^/]*$","" 
                  Get-sftp -List $path -Credential $Connection.Credential`
                                            
-Server $Connection.Server -force | 
                           Sort-Object -property @{e={-not $_.isdir}}, filename | 
                           Add-Member -MemberType NoteProperty   -Name "directory" -Value $directory -PassThru | 
                           Add-MemberMemberType ScriptProperty -Name "Path" `
                                        -Value {$this.directory + "/" + $this.Filename}PassThru
           }
           else {write-warning "We don't appear to have a valid connection."}
         }
}

The next things to add were copy-RemoteItem and Copy-LocalItem – which just have to call Get-SFTP and Send-SFTP with some pre-set parameters.

Function Copy-RemoteItem {
[CmdletBinding(SupportsShouldProcess=$true)]
param ([Parameter(ValueFromPipeLine=$true, ValueFromPipelineByPropertyName=$true, mandatory=$true)]
      
[String]$path,
       [String]$destination = $PWD,
       $Connection = $Global:ssh,
       [Switch]$force
       )
process { if ($Connection.server -and $Connection.Credential -and $path ) {
            
 #Because we re-make the connection in the Get-Sftp command we can pass an SSH object.
              write-verbose "Copying $path from $($Connection.Server) to $destination"
              Get-SFTP -RemoteFile $path -LocalFile $destination -Credential $Connection.Credential `
                       -Server $Connection.Server -Force -Overwrite:$force |
                       Add-Member -PassThru -MemberType Noteproperty -Name "Destination" -Value $destination
          }
          Else {Write-warning "We don't seem to have a valid destination and path" }
        }
}
Set-Alias -Name rCopy -Value Copy-RemoteItem

Function Copy-LocalItem {
[CmdletBinding(SupportsShouldProcess=$true)]
param ([Parameter(ValueFromPipeLine=$true, ValueFromPipelineByPropertyName=$true, mandatory=$true)]
       [String]$path,
       [Parameter(Mandatory=$true)][String]$destination ,
       $Connection = $Global:ssh,
       [Switch]$force
      )
process {  if ($Connection.server -and $Connection.Credential -and $path ) {
              
write-verbose "Copying $path to $destination on $($Connection.Server)"
               Send-SFTP -LocalFile $path -RemoteFile $destination -Credential $Connection.Credential `
                          -Server $Connection.Server -Force -Overwrite:$force
           }
           Else {Write-warning "We don't seem to have a valid destination and path" }
        }
}

The last wrapper I needed was one to go round Invoke-SSH – primarily to set default parameters, and return only the response text.

 Function Invoke-RemoteCommand {
[CmdletBinding(SupportsShouldProcess=$true)]
param   ([parameter(Mandatory=$true , ValueFromPipeLine=$true)][String]$CmdLine,
         $Connection = $Global:ssh
        )
process { if ($Connection -is [PowerShellInside.NetCmdlets.Commands.SSHConnection]) {
             write-verbose "# $cmdline"
             Invoke-SSH -Connection $Connection -Command $CmdLine |
              
 Select-Object -ExpandProperty text
          }
        }
}

Job done … well almost. I’ll talk about a couple of tricks I’ve used in combination with these in future posts.

Advertisements

Blog at WordPress.com.

%d bloggers like this: