Create folders with variables in the path (PowerShell)

Viewed 127

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 properly create more childpaths,they are created at the wrong path
mkdir $dir

edit:

Considering that the L folder has many subfolders, so I tried foreach nested, which created the folders correctly, but at the same time returned a lot of An item with the specified name...already exists.

$var = Read-Host -Prompt 'Type here'
$loc = "Cs\cz","Da\dk","Nl\nl","En\uk","En\us"
$dir = foreach ($childPath in "A\$var","I","L","M","U\Ed") {
    Join-Path $PSScriptRoot\$var $childPath
    foreach ($additionalChildPath in $loc) {
        Join-Path $PSScriptRoot\$var\L $additionalChildPath    
    }
}
mkdir $dir
2 Answers

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

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:

You don't strictly need Join-Path, because string operations may do in many cases. However, Join-Path is still worth considering for the following reasons:

  • It allows you to resolve wildcard-based paths to matching literal paths with the -Resolve switch.

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

If the path components are available as individual variables or strings, you can simply use an expandable (double-quoted) string ("...") to synthesize your path:

"$PSScriptRoot\$variable\A\I\L\M\U"

Note: The above hard-codes the path separator as \ - see below for cross-platform considerations.

If the path components are available as elements of an array use 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.

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