Home > Software engineering >  Create folders with variables in the path (PowerShell)
Create folders with variables in the path (PowerShell)

Time:06-27

I want to create folders like this with variables in the path, I tried join-path and it tells me that "System.Object[]" cannot be converted to type "System.String" required by parameter "ChildPath". The specified method is not supported.

$variables\A\$variables
$variables\I
$variables\L\Cs\cz
$variables\L\Da\dk
$variables\L\Nl\nl
$variables\L\En\uk
$variables\L\En\us
$variables\M
$variables\U\Ed

I'm just new to powershell and this is my script:

$variables = Read-Host -Prompt 'Type here'
$dir = Join-Path -path $PSScriptRoot\$variables -ChildPath "A","I","L","M","U" 
#-AdditionalChildPath I don't know how to create more ChildPath
mkdir $dir

CodePudding user response:

The -ChildPath parameter accepts only a single string, so you have to call Join-Path multiple times, if you'd like to individually join multiple child paths to a base path, creating an array of paths (if that is what you want):

$dir = foreach( $childPath in "A","I","L","M","U" ) {
    Join-Path -Path $PSScriptRoot\$variables -ChildPath $childPath
}

This captures all (implicit) output from the foreach loop body in variable $dir, automatically creating an array like this:

c:\foo\A
c:\foo\I
c:\foo\L
c:\foo\M
c:\foo\U

Alternatively you may take advantage of the fact that the -ChildPath parameter accepts pipeline input (as documented):

$dir = "A","I","L","M","U" | Join-Path -Path $PSScriptRoot\$variables -ChildPath { $_ }

The -ChildPath argument is a delay-bind script block that simply passes the current pipeline object to the Join-Path command. As in the foreach loop, Join-Path gets called for each of the letters that are passed as input to the pipeline. Again, all output is captured in $dir as an array.


In a comment you mention -AdditionalChildPath. This parameter actually accepts an array, but the -ChildPath parameter is still mandatory and must be specified as well:

Join-Path -Path A -ChildPath B -AdditionalChildPath C, D

Output is a single path:

A\B\C\D

This call syntax is somewhat inconvenient if you'd like to join an arbitrary number of child paths, defined as an array. For that you may use array splatting:

$childPaths = 'B','C','D'
Join-Path -Path A @childPaths

Output:

A\B\C\D

CodePudding user response:

To add to zett42's helpful answer:

  • In Windows PowerShell, use of a single Join-Path call isn't an option for joining more than two path components at a time; -ChildPath accepts only a single string, and the [string[]]-typed -AdditionalChildPath parameter only exists in PowerShell (Core) 7 .

    • A simple workaround is to use the -join operator on the multiple child paths in order to combine them into a single argument (see the next section about using -join instead of Join-Path and associated considerations):

      # Windows PowerShell workaround.
      Join-Path -path 'a' -ChildPath ('b', 'c', 'd' -join '\') # -> 'a/b/c/d'
      
    • As an aside: By contrast, the -Path parameter is [string[]]-typed (i.e., it accepts an array of string values), which means that each among multiple values passed to it is combined with the - one and only - -ChildPath value.

      Join-Path -path 'a', 'b' -ChildPath 'z' # -> 'a\z', 'b\z'
      
  • In PowerShell (Core) 7 , you can join an open-ended number of components, and the most convenient syntax is to pass all path components as individual, positional arguments, because the [string[]-typed -AdditionalChildPath parameter, which accepts an array of additional components, is defined with the ValueFromRemainingArguments property, meaning that it collects any (remaining) positional arguments (i.e., those not preceded by the target parameter name, such as -ChildPath).

    • Together with positionally binding the -Path and -ChildPath parameters, you can simply pass all components positionally, optionally in an array via splatting; e.g.:

      # PS v7  only
      
      # Individual arguments:
      # 'a' binds positionally to -Path
      # 'b' binds positionally to -ChildPath
      # 'c' and 'd' bind to -AdditionalChildPath via ValueFromRemainingArguments
      Join-Path a b c d # -> 'a\b\c\d'
      
      # Ditto, via array splatting.
      # You may combine splatting with passing the first (-Path) argument
      # individually, as in zett42's answer, or also the second one (`-ChildPath`)
      $allComponents = 'a', 'b', 'c', 'd'
      Join-Path @allComponents # -> 'a\b\c\d'
      

Alternatives to Join-Path:

Unless you need to resolve wildcard-based paths to matching literal paths with the -Resolve switch, you don't strictly need Join-Path, and simply using the -join operator on an array may be sufficient:

$allComponents = 'a', 'b', 'c', 'd'
$allComponents -join '\' # -> 'a\b\c\d'

# For cross-platform use (if necessary):
$allComponents -join [IO.Path]::DirectorySeparatorChar # -> 'a\b\c\d' or 'a/b/c/d'

Note:

  • PowerShell itself accepts \ and / interchangeably as path separators, so even on Unix-like platforms you can get away with using \, as long as it is only PowerShell cmdlets that interpret the resulting paths.

  • Arguments for Join-Path use:

    • It automatically uses the platform-appropriate path separator (\ vs. /)
    • It prevents duplicate path separators, so that Join-Path a\ b still yields 'a\b' on Windows, for instance.
      However, note that duplicate path separators (e.g. a\\b) are generally not a problem in that paths with them are still recognized properly.

For the sake of completeness: You may also use the [System.IO.Path]::Combine() method, which is cross-platform aware; however, there are pitfalls:

  • You must cast a regular PowerShell array (which is [object[]-typed) to [string[]] in order for PowerShell to find the desired method overload:

    $allComponents = 'a', 'b', 'c', 'd'
    # Note: [string[]] cast only required in Windows PowerShell.
    [IO.Path]::Combine([string[]] $allComponents) # -> 'a\b\c\d' or 'a/b/c/d'
    
    # However, you may pass *up to 4* arguments *individually*:
    [IO.Path]::Combine('a', 'b', 'c', 'd')
    
  • If a component other than the first one starts with a (platform-appropriate) path separator, the preceding components are ignored; e.g.:

    # Windows example
    [IO.Path]::Combine('a', 'b', '\c', 'd') # -> !! '\c\d'
    
  • Related