GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Join multiple paths in one call to Join-Path by dlwyatt · Pull Request #2014 · PowerShell/PowerShell

Conversation

@dlwyatt

@dlwyatt Dave Wyatt (dlwyatt) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor

Now that we've got cross-platform PowerShell, this is a quality of life improvement for script authors who want to make code compatible across Windows and Linux. In the current implementation (on Windows), if you want to join paths multiple levels deep, you'd do this:

Join-Path $someParentPath 'child1\child2\child3'

Since you knew what the entire child path needed to be, there was no need for "join" logic there. Now, we'd prefer to see a path completely built up with the proper directory separator character for the platform. That would have looked something like this mess:

Join-Path $someParentPath (Join-Path child1 (Join-Path child2 child3))

With this PR, it becomes much more pleasant:

Join-Path $someParentPath child1 child2 child3

Question for the team: do I need to update JoinPathActivity.cs in this PR to reflect the new string[] parameter type as well, or do those workflow activity files get automatically regenerated later?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

rkeithhill, ForNeVeR, jpsnover, andyleejordan, zhiqiangyudev, and skyguy94 reacted with thumbs up emoji
All reactions
  • 👍 6 reactions
@dlwyatt
Join multiple paths in one call
a194118
@msftclas

Microsoft Contribution License Agreements (msftclas) commented Aug 22, 2016

Copy link
Copy Markdown

Hi Dave Wyatt (@dlwyatt), I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by Microsoft and real humans are currently evaluating your PR.

TTYL, MSBOT;

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@msftclas Microsoft Contribution License Agreements (msftclas) added the cla-already-signed label Aug 22, 2016
lzybkr
lzybkr reviewed Aug 22, 2016
View reviewed changes
Comment thread src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs Outdated
[AllowEmptyString]
public string ChildPath { get; set; } = String.Empty;
[AllowEmptyCollection]
public string[] ChildPath { get; set; } = new string[0];

@lzybkr Jason Shirk (lzybkr) Aug 22, 2016

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To help GC a little, we use Utils.EmptyArray<string>() so we can share empty arrays.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@lzybkr Jason Shirk (lzybkr) Aug 22, 2016

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that changing the type on a public API is normally considered a breaking change.

That said, cmdlets are too hard to use as an API, so it's unlikely to break anybody.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dlwyatt Dave Wyatt (dlwyatt) Aug 22, 2016

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured that PowerShell will happily bind a string to a string[] parameter anyway, so no harm done. :) (Unless someone's using the cmdlet directly in C#,but that seems unlikely.)

On Aug 22, 2016, at 11:16 AM, Jason Shirk [email protected] wrote:

In src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs:

     [AllowNull]
     [AllowEmptyString]
  •    public string ChildPath { get; set; } = String.Empty;
    
  •    [AllowEmptyCollection]
    
  •    public string[] ChildPath { get; set; } = new string[0];
    
    Note that changing the type on a public API is normally considered a breaking change.

That said, cmdlets are too hard to use as an API, so it's unlikely to break anybody.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@rkeithhill Keith Hill (rkeithhill) Aug 22, 2016 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Unless someone's using the cmdlet directly in C#,but that seems unlikely

Yeah, unlikely for this cmdlet, given it would be easier to use Path.Combine() from C#. PS. Nice enhancement!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@lzybkr

Jason Shirk (lzybkr) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor

We have a tool to update the workflow activity files that we run periodically so that you don't need to worry about it every time you add a new parameter to a cmdlet.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr Jason Shirk (lzybkr) added WG-Cmdlets general cmdlet issues Changelog Needed labels Aug 22, 2016
@dlwyatt
Using Utils.EmptyArray for parameter default value
203ace0
@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor Author

Travis CI build blew up, not sure why.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr Jason Shirk (lzybkr) self-assigned this Aug 22, 2016
@lzybkr Jason Shirk (lzybkr) added Review - Needed and removed Review - Waiting on Author labels Aug 22, 2016
@lzybkr

Jason Shirk (lzybkr) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor

We've discussed this PR some internally, here is a summary - just points for discussion at this point.

  • The breaking change could avoided by added a new parameter like AdditionalChildPath with ValueFromRemainingArguments = true.
  • ChildPath accepting ValueFromRemainingArguments can be confusing, e.g. Join-Path root d1,d2 would work like Join-Path root "d1 d2"
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor Author

The breaking change could avoided by added a new parameter like AdditionalChildPath with ValueFromRemainingArguments = true.

True. The output from Get-Command / Get-Help would be a little bit more cluttered, but that's no big deal if there's concern about changing existing parameter types. (There might also be weird situations where someone leaves ChildPath empty or null but passes values to AdditionalChildPaths. What's the proper behavior there? root//additional/child/paths or root/additional/child/paths?)

ChildPath accepting ValueFromRemainingArguments can be confusing, e.g. Join-Path root d1,d2 would work like Join-Path root "d1 d2"

Not sure I understand this one. Did you mean Join-Path "d1" "d2", or was the single string literal intended? How is this different from any other parameter with ValueFromRemainingArguments enabled?

Personally, I like having ValueFromRemainingArguments. I don't know if I'm in the minority on this, but Join-Path is one of the cmdlets that I always use with arguments passed by position rather than by name. It would be more confusing to me to have to remember where to start including commas, instead of having a simpler call. Assuming we leave -ChildPath alone and add a third parameter, the calls look like:

Join-Path one two three,four,five

# versus

Join-Path one two three four five

I think the second syntax looks much nicer (and less error prone), but even if I had to go with the first option, it would still be useful.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr

Jason Shirk (lzybkr) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor

No, I meant what I wrote - try it:

#57 PS> & {
>>     param(
>>         [string[]]
>>         [Parameter(Position=0)]
>>         $Path,
>>
>>         [string[]]
>>         [Parameter(Position=1, ValueFromRemainingArguments)]
>>         $Extra)
>>     $Extra.Count;
>>     $Extra | % { "arg: $_"}
>> } root aa,bb
1
arg: aa bb

I do think your example is fine w/ no commas, but folks do learn to use commas, and sometimes it's surprising. This isn't specific to your usage, it applies to ValueFromRemainingArguments in general.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 22, 2016

Copy link
Copy Markdown
Contributor Author

That doesn't make any sense. Seems like a bug in the parameter binder to me. It shouldn't matter whether you pass remaining arguments or an explicit array, both should wind up with identical values in the command.

On Aug 22, 2016, at 7:20 PM, Jason Shirk [email protected] wrote:

No, I meant what I wrote - try it:

#57 PS> & {

param(
    [string[]]
    [Parameter(Position=0)]
    $Path,

    [string[]]
    [Parameter(Position=1, ValueFromRemainingArguments)]
    $Extra)
$Extra.Count;
$Extra | % { "arg: $_"}

} root aa,bb
1
arg: aa bb
I do think your example is fine w/ no commas, but folks do learn to use commas, and sometimes it's surprising. This isn't specific to your usage, it applies to ValueFromRemainingArguments in general.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 23, 2016 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Interestingly, this problem doesn't affect cmdlets, just PowerShell functions:

 & {
     param(
         [string[]]
         [Parameter(Position=0)]
         $Path,

         [string[]]
         [Parameter(Position=1, ValueFromRemainingArguments)]
         $Extra)
     $Extra.Count;
     for ($i = 0; $i -lt $Extra.Count; $i++)
     {
        "${i}: $($Extra[$i])"
     }
 } root aa,bb

<#
1
0: aa bb
#>

Add-Type -OutputAssembly $env:temp\testBinding.dll -TypeDefinition @'
    using System;
    using System.Management.Automation;

    [Cmdlet("Test", "Binding")]
    public class TestBindingCommand : PSCmdlet
    {
        [Parameter(Position = 0)]
        public string Root { get; set; }

        [Parameter(Position = 1, ValueFromRemainingArguments = true)]
        public string[] Extra { get; set; }

        protected override void ProcessRecord()
        {
            WriteObject(Extra.Length);
            for (int i = 0; i < Extra.Length; i++)
            {
                WriteObject(String.Format("{0}: {1}", i, Extra[i]));
            }
        }
    }
'@

Import-Module $env:temp\testBinding.dll

Test-Binding root aa,bb

<#
2
0: aa
1: bb
#>
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 23, 2016

Copy link
Copy Markdown
Contributor Author

Logged issue #2035 for the different binding behavior between cmdlets and functions.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 23, 2016

Copy link
Copy Markdown
Contributor Author

Binding bug should be fixed in #2038 .

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 23, 2016

Copy link
Copy Markdown
Contributor Author

Now that the binder thing is sorted out (even if the other PR isn't accepted due to being classified as a breaking change, it doesn't affect the Join-Path cmdlet anyway), looking back at this one. Would you prefer to change ChildPath back to the way it was and introduce a new parameter instead?

If so, what's your preference for behavior if someone passes arguments to the new parameter, but ChildPath is null or empty?

  • Throw an error.
  • Build the path with an empty element. (`/root//additional/paths')
  • Build the path, ignoring the fact that ChildPath is empty. (/root/additional/paths)

Come to think of it, the same question applies if someone passes empty strings at any point in the new parameter. Ignore them, or have some doubled separators in the output? /root/one//three versus /root/one/three.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mwrock

Matt Wrock (mwrock) commented Aug 24, 2016

Copy link
Copy Markdown
Contributor

👍 I really like this Dave Wyatt (@dlwyatt)! This style is similar to ruby's File.join that takes N strings and just joins them all. It always felt more concise and natural than powershell's current implementation.

ForNeVeR and jpsnover reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr

Jason Shirk (lzybkr) commented Aug 24, 2016

Copy link
Copy Markdown
Contributor

Regarding $null entries - it seems like it should be an a terminating error. Note that Join-Path supports piped arguments:

PS> 'a','b' | Join-Path -ChildPath c
a\c
b\c

And $null results in a non-terminating error. That said, if there are any $null values in -ChildPath, nothing from the pipeline would work, so a single terminating error seems better than multiple non-terminating errors.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 24, 2016

Copy link
Copy Markdown
Contributor Author

'a','b' | Join-Path -ChildPath $null works fine today, you get this:

a\
b\

For whatever reason, ChildPath is marked as ValueFromPipelineByPropertyName, so if we were going to produce errors about it, nonterminating is probably the correct option. Also, ChildPath has [AllowNull()] but Path does not. No idea why they are different, but I left that alone. Only difference now is that you can have multiple ChildPath strings, and there are a few options for how to handle null / empty values in the middle of that array rather than at the end.

I'm leaning toward ignoring empty values unless they're at the end of the list, in which case we'll go ahead and tack on a trailing separator to keep behavior consistent with today.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr

Jason Shirk (lzybkr) commented Aug 24, 2016

Copy link
Copy Markdown
Contributor

Well, I can't say I like it, but it's at least consistent with string.Join:

PS> [string]::Join("\", 'a', [NullString]::Value, 'c')
a\\c
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 24, 2016

Copy link
Copy Markdown
Contributor Author

True, but the benefit of Join-Path is that you don't have dumb logic like that. It looks to see if either of the strings have leading / trailing separators already so it doesn't double them up. :)

Join-Path '\a\b\' '\c\d'
\a\b\c\d
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 24, 2016 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

In fact, that's how SessionState.Path.Combine is already working, so I'd have to go out of my way to wind up with doubled separators in the string. Seems weird to add extra code to produce goofy output, so problem solved?

$path = ''
$array = 'a', [nullstring]::Value, 'c'
foreach ($p in $array) { $path = $ExecutionContext.SessionState.Path.Combine($path, $p) }
$path

a\c
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dlwyatt
AdditionalChildPath parameter on Join-Path
e24f3fa
@dlwyatt

Dave Wyatt (dlwyatt) commented Aug 27, 2016

Copy link
Copy Markdown
Contributor Author

As discussed, moved new code to a new parameter. Didn't bother putting a default value back on ChildPath since it's mandatory anyway.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr

Jason Shirk (lzybkr) commented Aug 31, 2016

Copy link
Copy Markdown
Contributor

Closed via 22aac12 - I deleted a blank line between the comments and a property.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lzybkr Jason Shirk (lzybkr) closed this Aug 31, 2016
@dlwyatt
Dave Wyatt (dlwyatt) deleted the JoinMultiPath branch October 12, 2017 18:25
@MartinGC94 MartinGC94 mentioned this pull request Mar 24, 2024
Make Join-Path parameter -ChildPath accept an array #21367
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@lzybkr Jason Shirk (lzybkr)

Labels

WG-Cmdlets general cmdlet issues

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@dlwyatt @msftclas @lzybkr @mwrock @rkeithhill
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.