PowerShell: WinSCP Module

$username='bongo'
$port=20202
$remoteHost='dev-sftp.kimconnect.net'
$privateKey='C:\scripts\keys\testkey.ppk'
$remoteRoot='/'
$downloadFolder='D:\downloads'
$remoteDirectory='/var/www/sftp.kimconnect.net'
$currentDirectory=$remoteDirectory
$localFolder=(New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path

$appName='winscp'  
function includePowerShellWrapper($appName){
    # Ensure that PowerShell is using TLS 1.2 in this session
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    # Uncomment these lines to make TLS1.2 defaults on next reboot
    #Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord
    #Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord    

    # Deterministic verification on whether module commands are able to load
    Import-Module $appName -ea SilentlyContinue  
    $moduleIsLoaded=Get-Command -Module $appName -ea SilentlyContinue
    
    # Sanity check: validate module name before proceeding
    $moduleVersion=.{$y=find-module $appName -ea SilentlyContinue;if($y){return $y.Version.ToString()}}
    if(!$moduleVersion){write-warning "'$($appName.ToUpper())' doesn't match PowerShell module";return $false}   
    elseif(!$moduleIsLoaded){
        # Part A) Compare the installed module with its online counter-part; install if necessary
        # Select Major, Minor, and Build - omitting Revision.
        # This is because PowerShell wrapper will deem an installed binary as compatible as long as those values match     
        $installedVersion=.{$x=Get-Module $appName -ea SilentlyContinue;if($x){return $x.Version.ToString()}}
        $targetVersion=.{try{[void]($moduleVersion -match '^(\d+\.\d+\.{0,1}\d+)');$matches[1]}catch{}}
        if($matches){Clear-Variable -name matches}
        write-host "$($appName.ToUpper()) PowerShell wrapper`: Installed $installedVersion v.s Updated version $moduleVersion"
        if($installedVersion -ne $moduleVersion){
            write-host "Attempting to install the module $appName $moduleVersion"
            Remove-Module $appName -ea SilentlyContinue
            try{                
                Install-Module -Name $appName -Force -Confirm:$false;
                Import-Module $appName;
                write-host "$appName PowerShell wrapper module has been installed."
                return $true
                }
            catch{
                if(!('NuGet' -in (get-packageprovider).Name)){    
                    try{
                        Install-PackageProvider -Name NuGet -MinimumVersion '2.8.5.201' -Force -Confirm:$false -ErrorAction Stop;
                        Install-Module -Name $appName -Force -Confirm:$false;
                        Import-Module $appName;
                        write-host "$appName PowerShell wrapper module has been installed."
                        }
                    catch{                  
                        write-host "$error"
                        write-warning "Unable to install $appName module"
                        return $false
                        }            
                    }
                }
            }

        # Part B) Check Installed applications and upgrade/downgrade as necessary
        $installedApps=Get-CimInstance -ClassName win32_InstalledWin32Program -ErrorAction SilentlyContinue|select Name,Version
        $appwizAppExists=.{try{$matchApp=$installedApps|?{$_.Name -like "*$appName*"|select -First 1 }
                            if($matchApp){$matchApp}else{$false}}catch{}}
        $appwizVersion=.{try{[void]($(if($appwizAppExists){$appwizAppExists.Version}else{$false}) -match '^(\d+\.\d+\.{0,1}\d+)');$matches[1]}catch{}}
        if($matches){Clear-Variable -name matches}
        write-host "Application Wizard version being detected: $appwizAppExists"
        $isAppwizSameVersion=$targetVersion -eq $appwizVersion
    
        $chocoInstalledApps=choco list -l
        $chocoAppExists=$chocoInstalledApps|?{$_ -like "*$appName*"}|select -First 1
        $chocoVersion=.{try{[void]($(.{[void]($chocoAppExists -match ' ([\d+\.]+)');return $matches[1]}) -match '^(\d+\.\d+\.{0,1}\d+)');$matches[1]}catch{}}
        if($matches){Clear-Variable -name matches}
        write-host "Choco version being detected: $chocoAppExists"
        $isChocoSameVersion=$targetVersion -eq $chocoVersion
        write-host "Target version: $targetVersion, Appwiz version: $appwizVersion, Choco version: $chocoVersion"
    
        if (!($isAppwizSameVersion -or $isChocoSameVersion)){        
            write-host "Now trying to install $appname..."
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
                try{
                    if(!$chocoAppExists -and !$appwizAppExists){choco install $appName --version=$targetVersion -y}
                    elseif (!$isChocoSameVersion -and !$isAppwizSameVersion){choco install $appName --version=$targetVersion --force -y}
                    write-host "Application $appName $targetVersion is now installed"
                    if($matches){Clear-Variable -name matches}
                    return $true
                }
                catch{
                    write-warning "Unable to install the app name $appName $targetVersion using Chocolatey."
                    if($matches){Clear-Variable -name matches}
                    return $false
                    }
            }
        else{
            write-host "$appName is already installed.";
            if($matches){Clear-Variable -name matches}
            return $true}
    }
    else{write-host "$($appName.ToUpper()) module has loaded successfully.";return $true}
}
includePowerShellWrapper $appName

    function connect-WinScp{
        param (
            $remoteHost,
            $username,
            $password,
            $port=22,
            $hostKey,
            $privateKey
            )
        Import-Module WinScp -ea SilentlyContinue
        # Possible error:
        # Import-Module : The specified module 'WinScp' was not loaded because no valid module file was found in any module
        # directory.
        # At line:102 char:15
        # +         $null=Import-Module WinScp
        # +               ~~~~~~~~~~~~~~~~~~~~
        #     + CategoryInfo          : ResourceUnavailable: (WinScp:String) [Import-Module], FileNotFoundException
        #     + FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
        #         $moduleIsLoaded=Get-Command -Module $appName -ea SilentlyContinue
        if(!(get-module Winscp)){
            includePowerShellWrapper Winscp
            Import-Module WinScp
            }
        if ($scpSession){ # deal with case that the global variable $scpSession has already been assigned
            try{
                Close-WinSCPSession -WinSCPSession $scpSession -ea SilentlyContinue
                $scpSession.Dispose()
            }catch{
                Clear-Variable scpSession
                }
            } 
         $optionValues="
            `$options = New-Object WinSCP.SessionOptions -Property @{
                # source: https://winscp.net/eng/docs/library_sessionoptions
                Protocol = [WinSCP.Protocol]::Sftp
                HostName = '$remoteHost'
                username = '$username'
                portnumber = '$port'
                $(if($hostKey){"SshHostKeyFingerprint = '$hostKey'"}else{"GiveUpSecurityAndAcceptAnySshHostKey = `$True"})
                $(if($privateKey){"SshPrivateKeyPath = '$privateKey'"}else{"password = '$password'"})
            }"
         try{
            write-host $optionValues
            Invoke-Expression $optionValues
            $scpSession =  Open-WinSCPSession -SessionOption $options -ea Stop
            write-host "WinSCP has successfully connected to $remoteHost" -ForegroundColor Yellow
            }
        catch{
            write-host $_
            write-warning "unable to connnect to $remoteHost"
            }
    }
connect-WinScp

function confirmation($content,$testValue="I confirm",$maxAttempts=3){
    $confirmed=$false;
    $attempts=0;        
    $content|write-host
    write-host "Please review this content for accuracy.`r`n"
    while ($attempts -le $maxAttempts){
        if($attempts++ -ge $maxAttempts){
            write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
            break;
            }
        $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm";
        if ($userInput.ToLower() -ne $testValue.ToLower()){
            cls;
            $content|write-host
            write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again..`r`n"
            }else{
                $confirmed=$true;
                write-host "Confirmed!`r`n";
                break;
                }
        }
    return $confirmed;
}

# Mapping LiSt remote directory command
function ls-WinScp($directory,$session=$scpSession){
    if (!$directory){$directory=$currentDirectory}
    $remoteDir = $session.ListDirectory($directory)
    foreach ($fileInfo in $remoteDir.Files){
        Write-Host ("{0}{1} {2,9} {3,-12:MMM dd HH:mm:ss yyyy} {4}" -f
            $fileInfo.FileType, $fileInfo.FilePermissions, $fileInfo.Length,
            $fileInfo.LastWriteTime, $fileInfo.Name) -ForegroundColor Yellow
    }
}

# Mapping change directory command
function cd-Winscp([string]$directory){
    if ($directory -ne $null){
        $GLOBAL:currentDirectory=$directory
        write-host "current directory has been changed to $currentDirectory"
        write-host $currentDirectory -ForegroundColor Yellow
        }
    else{
        $GLOBAL:currentDirectory='~'
        write-host "current directory has been changed to $currentDirectory"
        }
}

# Mapping Make Directory command
function mkdir-WinScp ($directoryName,$session=$scpSession){
    $inputIsPath=$directoryName[0] -eq '/'
    $inputIsName=$directoryName -notMatch '/'
    if ($inputIsPath){
        $session.CreateDirectory($directoryName)
        write-host $directoryName+' has been created' -ForegroundColor Yellow
        }
    elseif($inputIsName){
        $fullPath=$currentDirectory+$directoryName
        $session.CreateDirectory($fullPath)
        write-host $fullPath+' has been created' -ForegroundColor Yellow
        }
    else{write-host "Input value '$directoryName' is invalid"}
}

# Mapping Remove Item command
function removeItem-WinScp($remoteItem,$session=$scpSession){
    $inputIsPath=$remoteItem[0] -eq '/'
    $inputIsName=$remoteItem -notMatch '/'
    if ($inputIsPath){
        $confirmation=confirmation "DELETE $remoteItem`?"
        if($confirmation){$session.RemoveFiles("$remoteItem")
            write-host $remoteItem+' has been removed' -ForegroundColor Yellow
            }
        else{write-host "$remoteItem NOT removed."}
        }
    elseif($inputIsName){
        $fullPath=$currentDirectory+'/'+$remoteItem
        $confirmation=confirmation "DELETE $fullPath?"
        if($confirmation){$session.RemoveFiles($fullPath)
            write-host $fullPath+' has been removed' -ForegroundColor Yellow
            }
        else{write-host "$fullPath NOT removed."}
        }
    else{write-host "Input value '$remoteItem' is invalid"}
}

function exit-WinScp($session=$scpSession){
    try{
        Close-WinSCPSession -WinSCPSession $session -ea SilentlyContinue
        $session.Dispose()
        write-host "Session disconnected" -ForegroundColor Yellow
        }
    catch{
        Write-Warning "Error: $($_.Exception.Message)"
        }
    }

function changeLocal-WinScp($path,$session=$scpSession){
    $GLOBAL:localFolder=$path
    write-host "Current local folder has been set as: $localFolder" -ForegroundColor Yellow
}

function download-WinScp($remotePath=$currentDirectory,$localPath=$localFolder,$removeFromSource=$false,$session=$scpSession){
    if($session){
        try{
            $command="Receive-WinSCPItem -WinSCPSession $session -RemotePath '$remotePath' -LocalPath '$localPath' $(if($removeFromSource){'-remove'})"
            invoke-command $command
            }
        catch{
            write-warning "$($Error[0])"
            }
        }
    else{write-warning "There are no active connections to perform that action."}
}

function upload-WinScp($localPath=$localFolder,$remotePath=$currentDirectory,$removeFromSource=$false,$session=$scpSession){
    if($localPath -eq $localFolder){$localPath+='\*'}
    elseif(!(test-path $localPath)){write-warning "Invalid local path: $localPath";break}
    else{if($localPath[$localPath.Length-1] -eq '\' -and (Get-Item $localPath) -is [System.IO.DirectoryInfo]){$localPath+='\*'} }
    if($session){
        try{
            $command="Send-WinSCPItem -WinSCPSession `$session -LocalPath '$localPath' -RemotePath '$remotePath' $(if($removeFromSource){'-remove'})"
            write-host $command
            pause
            Invoke-Expression $command -ea Stop
            }
        catch{
            write-warning "$($Error[0])"
            }
        }
    else{write-warning "There are no active connections to perform that action."} 
}

Leave a Reply

Your email address will not be published. Required fields are marked *