Reputation: 35
So I'm trying to create a "download" function that uses a piped object property to determine a download method (sftp or http). Then either create an sftp script for putty/winscp or curl the http url. I am defining objects as follows:
#WinSCP
$winscp = new-object psobject
$winscp | add-member noteproperty name "WinSCP"
$winscp | add-member noteproperty dltype "http"
$winscp | add-member noteproperty file "winscp.exe"
$winscp | add-member noteproperty url "https://cdn.winscp.net/files/WinSCP-5.17.8-Setup.exe"
$winscp | add-member noteproperty path "$env:ProgramFiles(x86)\WinSCP"
$winscp | add-member noteproperty install 'msiexec /i "$DataPath\$winscp.file" /quiet /norestart'
#Database
$db = new-object psobject
$db | add-member noteproperty name "Client Database"
$db | add-member noteproperty dltype "sftp"
$db | add-member noteproperty file "database_"
$db | add-member noteproperty ver "check"
$db | add-member noteproperty ext ".csv"
$db | add-member noteproperty dir "db"
#DatabaseVersion
$db_ver = new-object psobject
$db_ver | add-member noteproperty name "Database Version File"
$db_ver | add-member noteproperty dltype "sftp"
$db_ver | add-member noteproperty file "current_version.txt"
$db_ver | add-member noteproperty dir "db"
Currently I'm having issues with the $Input variable within the function. It can only be used once and does not translate into an if statement. Since it contains an object with multiple properties, it needs converted to a new object within the function first I think. I'm new to powershell and haven't found a way of doing this yet. Here is the function I made and am trying to use:
function Download () {
#HTTP Download Method
if ($input.dltype -eq "http") {
curl $input.url -O $DataPath\$input.file
#HTTP Success or Error
$curlResult = $LastExitCode
if ($curlResult -eq 0)
{
Write-Host "Successfully downloaded $input.name"
}
else
{
Write-Host "Error downloading $input.name"
}
pause
}
#SFTP Download Method
if ($input.dltype -eq "sftp") {
sftpPassCheck
#Detect if version required
if ($input.ver = "check") {
#Download the objects version file
"$+$Input+_ver" | Download
#Update the object's ver property
$input.ver = [IO.File]::ReadAllText("$DataPath\current_version.txt")
#Build the new filename
$input.file = "$input.file"+"$input.ver"+"$input.ext"
#Delete the version file
Remove-Item "$DataPath\current_version.txt"
}
& "C:\Program Files (x86)\WinSCP\WinSCP.com" `
/log="$DataPath\SFTP.log" /ini=nul `
/command `
"open sftp://ftpconnector:$script:sftp_pass@$input.ip/ -hostkey=`"`"ssh-ed25519 255 SETvoRlAT0/eJJpRhRRpBO5vLfrhm5L1mRrMkOiPS70=`"`" -rawsettings ProxyPort=0" `
"cd /$input.dir" `
"lcd $DataPath" `
"get $input.file" `
"exit"
#SFTP Success or Error
$winscpResult = $LastExitCode
if ($winscpResult -eq 0)
{
Write-Host "Successfully downloaded $input.name"
}
else
{
Write-Host "Error downloading $input.name"
}
}
}
I'm probably missing something simple but I'm clueless at this point. Oh usage should be:
WinSCP | download
Upvotes: 2
Views: 163
Reputation: 174795
mklement0 is spot on - $input
is not really meant to used directly, and you're probably much better off explicitly declaring your input parameters!
In addition to the $InputObject
pattern shown in that answer, you can also bind input object property values to parameters by name:
function Download
{
param(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[Alias('dltype')]
[string]$Protocol = 'http'
)
process {
Write-Host "Choice of protocol: $Protocol"
}
}
Notice that although the name of this parameter is $Protocol
, the [Alias('dltype')]
attribute will ensure that the value of the dltype
property on the input object is bound.
The effect of this is:
PS ~> $WinSCP,$db |Download
Choice of protocol: http
Choice of protocol: sftp
Keep repeating this pattern for any required input parameter - declare a named parameter mapped to property names (if necessary), and you might end up with something like:
function Download
{
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateSet('sftp', 'http')]
[Alias('dltype')]
[string]$Protocol,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[Alias('dir')]
[string]$Path = $PWD,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('url','file')]
[string]$Uri
)
process {
Write-Host "Downloading $Uri to $Path over $Protocol"
}
}
Now you can do:
PS ~> $WinSCP,$db |Download
Downloading https://cdn.winscp.net/files/WinSCP-5.17.8-Setup.exe to C:\Program Files(x86)\WinSCP over http
Downloading database_ to db over sftp
We're no longer dependent on direct access to $input
, $InputObject
or $_
, nice and clean.
Please see the about_Functions_Advanced_Parameters
help file for more information about parameter declaration.
Upvotes: 3
Reputation: 439597
The proper way to bind input from the pipeline to a function's parameters is to declare an advanced function - see about_Functions_Advanced_Parameters and the implementation in the bottom section of this answer.
However, in simple cases a filter
will do, which is a simplified form of a function that implicitly binds pipeline input to the automatic $_
variable and is called for each input object:
filter Download {
if ($_.dltype -eq "http") {
# ...
}
}
$input
is another automatic variable, which in simple (non-advanced) function
s is an enumerator for all pipeline input being received and must therefore be looped over.
That is, the following simple function
is the equivalent of the above filter
:
function Download {
# Explicit looping over $input is required.
foreach ($obj in $input) {
if ($obj.dltype -eq "http") {
# ...
}
}
}
If you do want to turn this into an advanced function (note that I've changed the name to conform to PowerShell's verb-noun naming convention):
function Invoke-Download {
param(
# Declare a parameter explicitly and mark it as
# as pipeline-binding.
[Parameter(ValueFromPipeline, Mandatory)]
$InputObject # Not type-constraining the parameter implies [object]
)
# The `process` block is called for each pipeline input object
# with $InputObject referencing the object at hand.
process {
if ($InputObject.dltype -eq "http") {
# ...
}
}
}
Upvotes: 3