Saturday, May 12, 2018

Powershell Script - Invoke-RemoteShellCommand - Run remote Linux bash commands with output wrapper

Recently, I've been working much more with linux servers and I even challenged myself to run Ubuntu on my primary personal laptop while still doing mostly powershell development.  I needed a way to quickly scale powershell core deployment out to servers, so I came up with a little wrapper function which simplifies this task leveraging putty's plink ssh client.  Her'e's what I came up with.

<#
BV-LinuxSSHRemoting.ps1
https://www.bryanvine.com/2018/05/powershell-script-invoke.html
Author: Bryan Vine
Last updated: 05/1/2018
Description: This function simplifies running linux shell commands over SSH while returning the output
#>
#Requires -version 2
Function Invoke-RemoteShellCommand{
<#
.SYNOPSIS
Uses Putty's Plink to remotely execute commands via SSH and sudo
.DESCRIPTION
Wrapper function that simplifies sending remote shell commands to run as sudo and returning the output.
New servers will need to have their key accepted upon the first connection, but won't prompt again after.
Obviously requires SSH server is enabled on the target host(s).
.PARAMETER Host
Target Server(s) or IP(s) to execute the command on.
.PARAMETER Username
Username on the linux server(s).
.PARAMETER Password
Password for the linux user (not ideal, better to use Credentials instead).
.PARAMETER Credentials
Secure system credentials for the linux server(s).
.PARAMETER Command
Shell command to be ran.
.PARAMETER NoSudo
By default, commands run under sudo. Including this switch makes the command run without sudo
.PARAMETER Options
Additional Parameters for plink (see putty plink documentation)
.EXAMPLE
Invoke-RemoteShellCommand -Credentials $creds -Target ubuntu01 -Command 'apt-get install powershell'
This will connect to ubuntu01 uses the $creds user & password and install powershell core.
.LINK
https://www.bryanvine.com/2018/05/powershell-script-invoke.html
.LINK
https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
.NOTES
Author: Bryan Vine
Last updated: 05/1/2018
#>
[cmdletbinding()]
Param(
[ValidateNotNullorEmpty()][Parameter(Position=0,Mandatory)][String[]]$Host,
[Parameter(Position=1,Mandatory,ParameterSetName='ClearTextAuth')][String]$Username,
[Parameter(Position=2,Mandatory,ParameterSetName='ClearTextAuth')][String]$Password,
[Parameter(Position=1,Mandatory,ParameterSetName='SecureAuth')][String]$Credentials,
[ValidateNotNullorEmpty()][Parameter(Position=4,Mandatory,ValueFromPipeline=$true)][String[]]$Command,
[Parameter(Position=3)][String]$Options,
[switch]$NoSudo
)
Begin{
#Test if putty plink exists
if(!(Test-Path 'C:\Program Files\PuTTY\plink.exe') -and !(Test-Path '.\plink.exe')){
Write-Output 'No Putty utilities installed! Please install from https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html'
Write-Error "Please install plink.exe utility from putty or place the plink.exe file in the same directory"
exit "No Plink"
}
if($Credentials){
$Username = $Credentials.Username.replace("\","")
$Password = $Credentials.GetNetworkCredential().password
}
$Commands = @()
$Commands += $Command
}
Process{
$Commands += $_
}
End{
$Host | %{
$runcommand = "plink -pw $Password -ssh $Username@$_ $Options "
if(!$NoSudo){
$runcommand += "echo -e `'$Password\n`' | sudo -S "
}
Write-Host -foregroundcolor Green "Executing on $_ as $Username"
$Commands | %{
$runthis = $runcommand + $_
. $runthis
}
}
}
}
Set-Alias -Name 'IRSC' -Value 'Invoke-RemoteShellCommand'


Powershell Script - 7zip/unzip powershell native replacement

Last year, I was tasked with creating a replacement for unzip executables in our environment with native powershell/.net extraction function. Because our environment used to heavily rely on both 7z.exe and unzip.exe (code has been updated so it's not anymore), I made a series of functions which mimic a limited set of parameter behavior for these executables. Here's what I came up with.

<#
BV-ZipUtility.ps1
http://www.bryanvine.com/2017/03/powershell-script-7zipunzip-powershell.html
Author: Bryan Vine
Last updated: 03/27/2017
Description: This collection of functions can be used as a module or imported in other
modules to replace most of the functionality of 7zip.exe and unzip.exe.
#>
#Requires -version 3
Function Test-DotNet{
<#
.SYNOPSIS
Checks for a specific .NET framework
.DESCRIPTION
Uses registry keys to find all installed versions of .NET framework and check that the highest version is
greater than or equal to a minimum level. Returns true if it is, false if it isn't.
.PARAMETER MinLevel
Minimum level of .NET framework required to return true.
.EXAMPLE
Test-DotNet
This will return true if .NET 4.5 or greater is installed.
.EXAMPLE
Test-DotNet -MinLevel 4.0
This will return true if .NET 4.0 or greater is installed.
.LINK
http://www.bryanvine.com/2017/03/powershell-script-7zipunzip-powershell.html
.NOTES
Author: Bryan Vine
Last updated: 03/27/2017
#>
[cmdletbinding()]
Param(
$MinLevel = 4.5
)
((Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse |
Get-ItemProperty -name Version -ErrorAction SilentlyContinue |
Where {$_.PSChildName -match '^(?!S)\p{L}'} |
Select -ExpandProperty Version -Unique |
Sort -Descending | Select -First 1) -ge $MinLevel)
}
function Compress-toZip{
<#
.SYNOPSIS
Compresses files and folders into a zip archive.
.DESCRIPTION
Creates new a new archive or updates an existing archive from either an array of files or a directory (or array of directories).
Requires powershell v3+ and .NET 4.5 framework.
.PARAMETER Zipfile
Output name of new zip archive or existing name of archive. Full local path or UNC path prefered for best results.
.PARAMETER Source
Input File(s) or Directory/Directories which are to be compressed into the archive
.PARAMETER CompressionLevel
Amount of compression to apply. Greatest to least: 'Optimal','Fastest','NoCompression'
.PARAMETER IncludeBaseDirectory
Specifies when creating new zip archive to include the base folder when specifying an input directory.
.PARAMETER OverwriteZip
If an existing output zip archive exists, it will delete it first before creating a new one.
.EXAMPLE
Compress-toZip -Zipfile 'test.zip' -Source 'Backup'
This will create a new zip file called test.zip in the current directory with the contents of the Backup folder.
.EXAMPLE
Compress-toZip -Zipfile 'c:\temp\test.zip' -Source '\\server1\Backup'
This will create a new zip file called test.zip in c:\temp with the contents of the Backup share from Server1.
.LINK
http://www.bryanvine.com/2017/03/powershell-script-7zipunzip-powershell.html
.NOTES
Author: Bryan Vine
Last updated: 03/27/2017
#>
[cmdletbinding()]
Param(
[ValidateNotNullorEmpty()][Parameter(Position=0,Mandatory)][String]$Zipfile,
[ValidateScript({Test-Path $_})][Parameter(Position=1,Mandatory)][String[]]$Source,
[Validateset('Optimal','Fastest','NoCompression')]$CompressionLevel = 'Optimal',
[switch]$IncludeBaseDirectory,
[switch]$OverwriteZip
)
#Check for .NET 4.5+
if(!(Test-DotNet)){
Write-Error "ERROR: Unable to perform zip operation, .NET 4.5+ not detected!"
return 1
}
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
#Input Normalization
if($Zipfile -notlike '*.zip'){$Zipfile += '.zip'}
if($Zipfile -notlike '*\*'){$Zipfile = $pwd.Path + '\' + $Zipfile}
if($Zipfile -like '.\*'){$Zipfile = $pwd.Path + $Zipfile.Substring(1)}
#Update existing Zip
if((Test-Path $Zipfile) -and !$OverwriteZip){
Write-Verbose "Updating existing zip file"
$zip = [System.IO.Compression.ZipFile]::Open($Zipfile,"Update")
#Remove existing files which will be updated in zip
$SourceFiles = dir -Recurse $Source |?{$_.mode -notlike "d*"}| select -ExpandProperty fullname
$Overlapfiles = $SourceFiles | %{$_.replace(($Source + '\'),'')}
$duplicates = $zip.Entries | ?{$Overlapfiles -contains $_.FullName}
$duplicates | %{$_.Delete()}
$SourceFiles | %{
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($Zip, $_, ($_.replace(($Source + '\'),'')),
($CompressionLevel)) | Out-Null
}
$zip.dispose()
#New or overwrite zip completely
}else{
#Overwrite
if($OverwriteZip){Remove-Item $Zipfile -Verbose}
#Source is a directory
if(test-path $Source -PathType Container){
$Source = dir $Source | select -First 1 -ExpandProperty PSParentPath | %{$_.Replace('Microsoft.PowerShell.Core\FileSystem::','')}
[System.IO.Compression.ZipFile]::CreateFromDirectory($Source, $Zipfile,
([System.IO.Compression.CompressionLevel]::$CompressionLevel), $IncludeBaseDirectory)
#Source is a single file
}else{
$zip = [System.IO.Compression.ZipFile]::Open($Zipfile,"Update")
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($Zip, $Source, (dir $Source|select -ExpandProperty name),
($CompressionLevel))
$zip.dispose()
}
}
}
function Expand-fromZip{
<#
.SYNOPSIS
Expands files and folders from a zip archive.
.DESCRIPTION
Extracts files and folders from a zip file to a selected destination folder. UNC paths work also.
Requires powershell v3+ and .NET 4.5 framework.
.PARAMETER Zipfile
Output name of new zip archive or existing name of archive. Full local path or UNC path prefered for best results.
.PARAMETER Destination
Extration folder or path. UNC also supported. If the destination path doesn't exist, it will be created.
.EXAMPLE
Expand-fromZip -Zipfile 'test.zip' -Destination 'Restored_Backup'
This will extra the contents from test.zip into the folder Restored_Backup. If the folder doesn't exist, it will be created.
.EXAMPLE
Expand-fromZip -Zipfile 'c:\temp\test.zip' -Destination '\\server1\Backup'
This will extract the contents of the zip file called test.zip in c:\temp to the Backup share from Server1.
.LINK
http://www.bryanvine.com/2017/03/powershell-script-7zipunzip-powershell.html
.NOTES
Author: Bryan Vine
Last updated: 03/27/2017
#>
[cmdletbinding()]
Param(
[ValidateScript({Test-Path $_})][Parameter(Position=0,Mandatory)][String]$Zipfile,
[Parameter(Position=1)][String]$Destination
)
#Check for .NET 4.5+
if(!(Test-DotNet)){
Write-Error "ERROR: Unable to perform zip operation, .NET 4.5+ not detected!"
return 1
}
[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
if(!($Destination)){
$Destination = $PWD
}
if(!(Test-Path $Destination)){
$Destination = (New-Item $Destination -ItemType Directory).FullName
}
if($Destination -notlike "*\*" -or $Destination -like ".\*"){
$Destination = dir $PWD | ?{$_.name -like $Destination -or $_.name -like $Destination.replace('.\','')} | select -ExpandProperty FullName
#select -First 1 -ExpandProperty PSParentPath | %{$_.Replace('Microsoft.PowerShell.Core\FileSystem::','')}
}
[System.IO.Compression.ZipFile]::ExtractToDirectory($Zipfile, $Destination)
}
Function Invoke-SevenZip{
<#
.SYNOPSIS
Wrapper function which calls both Compress-toZip and Expand-fromZip
.DESCRIPTION
This function effectively translates the parameters that overlap with both 7z.exe and unzip.exe.
Using the set-alias cmdlets, you can assign this function to '7z' and 'unzip' for easy substitution.
.PARAMETER CommandMode
Specifies which operation to apply. 'u' or 'a' specify compress, 'x' or 'e' specify expand.
.PARAMETER CompressionLevel
This sets the level of compression. The native .NET extensions only support 3 levels of compression.
.PARAMETER ZipFile
Specifies the zip file name to extract from or compress to.
.PARAMETER Directory
Specifies the directory (or source file) to compress from or extract to.
.PARAMETER Recurse
This switch enables recursion for compressing a target directory.
.EXAMPLE
Invoke-SevenZip a 'c:\temp\test.zip' '\\server\share\data' -m 3
This example compresses the contents of the data folder on server using the highest compression into the test.zip file locally.
.EXAMPLE
Invoke-SevenZip x 'c:\temp\test.zip' '.\bob'
This extracts the contents of test.zip into a folder named bob in the current directory.
.LINK
http://www.bryanvine.com/2017/03/powershell-script-7zipunzip-powershell.html
.LINK
Compress-toZip
.LINK
Expand-fromZip
.NOTES
Author: Bryan Vine
Last updated: 03/27/2017
#>
[cmdletbinding()]
Param(
[Validateset('a','e','u','x')][Parameter(Position=0,Mandatory)][String]$CommandMode,
[Parameter(Position=1,Mandatory)][String]$ZipFile,
[Parameter(Position=2)][String[]]$Directory,
[Validateset('5','4','3','2','1','0','Optimal','Fastest','NoCompression')][Alias("m")]$CompressionLevel = 'Optimal',
[Alias("r")][Switch]$Recurse
)
switch($CompressionLevel){
"5" {$CompressionLevel = 'Optimal'}
"4" {$CompressionLevel = 'Optimal'}
"3" {$CompressionLevel = 'Optimal'}
"2" {$CompressionLevel = 'Optimal'}
"1" {$CompressionLevel = 'Fastest'}
"0" {$CompressionLevel = 'NoCompression'}
}
if(!($Directory)){
$Directory = $PWD
}
if($CommandMode -like 'u' -or $CommandMode -like 'a'){
Compress-toZip -Zipfile $ZipFile -Source $Directory -CompressionLevel $CompressionLevel
}
if($CommandMode -like 'e' -or $CommandMode -like 'x'){
Expand-fromZip -Zipfile $ZipFile -Destination $Directory
}
}
Set-Alias -Name '7z' -Value 'Invoke-SevenZip'
Set-Alias -Name 'Unzip' -Value 'Invoke-SevenZip'