As you state yourself:
when you copy the active line to the clipboard, a trailing line will be added, in PowerShell this results in a multi-line clipboard, ie an array
That is indeed the cause of your problem (assuming that you include the newline in the selection you copy), where the Get-Clipboard cmdlet simply states for Output:
String
This cmdlet returns a string containing the contents of the clipboard.
The Get-Content cmdlet is more specific for the Output (Document issue: #12471):
String
By default, this cmdlet returns the content as an array of strings, one per line. When you use the Raw parameter, it returns a single string containing every line in the file.
ℹ️ Note
Both these cmdlets support the -Raw parameter which partly resolves your issue as it returns a single (multiline) string, but leaves you with the some newline characters at the end (which might be resolved with $Uri.Trim()).
The later String definition actual also applies to the output of the Get-Clipboard cmdlet and is common behavior for cmdlets that are written according the Write Single Records to the Pipeline best practice.
When you capture a PowerShell pipeline in a variable or parameter argument (using foo -ur (get-clipboard)) or if you use the Grouping operator ( ), PowerShell puts the whole pipeline in an array:
$Uri = 'MyUri', ''
$Uri.PSTypeNames
System.Object[]
System.Array
System.Object
Presuming that you would need some url checking anyways, you might consider error handling like:
$Uri | ForEach-Object {
if (([Uri]$_).Scheme) { "Processing $_" }
elseif ($_) { Write-Error "Incorrect Uri: $_" }
}
But as you apparently want to support the PowerShell pipeline (ValueFromPipeline), which means a whole lot more than using the pipe character (|) in a syntax, you probably want to write your PowerShell function (cmdlet) according the best practice Support Input from the Pipeline and Support the ProcessRecord Method.
This means that you would need to use the Process block:
This block is used to provide record-by-record processing for the function. You can use a process block without defining the other blocks. The number of process block executions depends on how you use the function and what input the function receives.
function foo{
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]$Uri
)
process {
if (([Uri]$_).Scheme) { "Process $_" }
elseif ($_) { Write-Error "Incorrect Uri: $_" }
}
}
ℹ️ Note
The automatic variable $_ or $PSItem contains the current object in the pipeline for use in the process block, but you might also use the $Uri parameter which also contains each single pipeline item (string aka line) in this process block.
The beauty of using the pipeline is that you process each URI one-by-one:
'https://stackoverflow.com/q/79806303', 'Not a Uri', 'https://www.wikipedia.org/', '' | Foo
Process https://stackoverflow.com/q/79806303/1701026
foo: Incorrect Uri: Not a Uri
Process https://www.wikipedia.org/
Anyways, if you also would like to support a multiline (array) as a parameters argument (foo -ur (get-clipboard)), you need to change your parameter to accept multiple strings ([string[]]$Uri) and process and iterate (foreach) through the specific $Uri variable:
function foo{
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string[]]$Uri
)
process {
$Uri | Foreach-Object {
if (([Uri]$_).Scheme) { "Process $_" }
elseif ($_) { Write-Error "Incorrect Uri: $_" }
}
}
}
ℹ️ Note
As commented by mklement0, using the string array parameter type ([String[]]) might increased overhead when processing pipeline input, which is still processed one by one, with a call to the process block for each input object, each of which - when accessed via $Uri - will then be a single-element array. This somewhat unfortunate situation is discussed in GitHub issue #21354
💡Tip
If performance is a concerning, you might also consider to use the foreach statement rather than wrapping the ForEach-Object cmdlet in the process block. See also: Avoid wrapping cmdlet pipelines
$Input
With regards to your last example ($input=get-clipboard; foo -ur $input), this is a common PowerShell gotcha: Don't use the $Input variable to assign any value because the $Input variable is an automatic variable. See: about automatic variables).
[URI]you could use as input type for your function. If you're input is not the proper type your function expects I'd make sure in advance that it is and not leave it to the function you want to process the input. 🤷🏼♂️$input, it's special. Assign your input to any other variablefoofunction wouldn't produce the symptoms you describe. Instead, it would output the last input line piped to it, which in the case of a single line followed by a newline on the clipboard would be an empty one.