2

ff I have a function foo, it needs to accept a string, this string can be passed to it directly by typing at the terminal, from a variable or from the clipboard.

function foo{
    [CmdletBinding()]
    Param(
      [parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string]$Uri
    )
    $Uri
}

Simple enough right? but for some reason today that is not so for me. Where am stuck on is the clipboard, in many applications, such as vscode, 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;

get-clipboard | foo                        #error Set-Man: Cannot process argument transformation on parameter 'Uri'. Cannot convert value to type System.String.
foo -ur (get-clipboard)                    #error Set-Man: Cannot process argument transformation on parameter 'Uri'. Cannot convert value to type System.String.
$input=get-clipboard; foo -ur $input       #error Set-Man: Cannot process argument transformation on parameter 'Uri'. Cannot convert value to type System.String.

no matter what I do, Powershell will just fail the function, its not like i can account for it, by joining, -join, inside my functions body, PowerShell fails the function from the outset.

I supposed I can go with [string[]] but this fundamentally changes my function and I lose control of what is coming in. and solutions like $input = (get-clipboard) -join "`n" is just undesirable.

Am on powershell 7.4.

4
  • 1
    There's a type [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. 🤷🏼‍♂️ Commented Nov 1 at 9:19
  • 1
    Your main issue is switching to [string[]]. Just use a wrapper function which will handle the clipboard Commented Nov 1 at 9:45
  • 3
    Don't assign to $input, it's special. Assign your input to any other variable Commented Nov 1 at 13:17
  • 1
    As an aside: your foo function 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. Commented Nov 1 at 17:22

2 Answers 2

4

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).

Sign up to request clarification or add additional context in comments.

Comments

0

if you are getting a two-item ARRAY, you can just grab line zero.

if you are getting a two-line string with a blank 2nd line, you can just use .Trim().

examples below ...

$TwoLineString = @'
2-line String - first line

'@

$TwoItemArray = @(
'Two-Item array ----- 1st line'
''
)

'=' * 20
$TwoLineString
'=' * 10
'grab just the 1st line IF the rest is just a blank line...'
$TwoLineString.Trim()
'char count = {0}' -f $TwoLineString.Trim().Length
'*' * 20
$TwoItemArray
'=' * 10
'grab ONLY the 1st item ...'
$TwoItemArray[0]
'char count = {0}' -f $TwoItemArray[0].Length
'=' * 20

results from the above code demoing a way to handle EITHER a 2-item array OR a two line string with a blank 2nd line.

====================
2-line String - first line

==========
grab just the 1st line IF the rest is just a blank line...
2-line String - first line
char count = 26
********************
Two-Item array ----- 1st line

==========
grab ONLY the 1st item ...
Two-Item array ----- 1st line
char count = 29
====================

2 Comments

Your answer presupposes that the target function receives the input as an array, whereas the premise of the question is that the function fails to do so.
i posted the wrong bit of code. [blush] \\\ the new code above shows how to handle the two variants that i saw - a two item array OR a two-line string with a blank 2nd line. \\\ did i misunderstand the problem that completely? [grin]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.