Search code examples
powershellpowershell-5.0

New-Item changes function return value


I want to write a function that creates a folder (if it doesn't exist) with specified name inside specified folder. It's turned out that depending on calling New-Item function return different values. And I can't figure out how it's related to New-Item actually.

$folderPath = "C:\tmp"

function CreateFolder([string] $name, [string] $parentFolder) 
{
  $path = "$parentFolder\$name"

  if(!(Test-Path $path))
  {
    New-Item -path $parentFolder -name $name -itemtype Directory
  }

  return $path
}

$FOLDER_NAME = "folder1"

$destination = CreateFolder $FOLDER_NAME $folderPath

echo $destination.GetType()

If folder1 doesn't exist it'll return:

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

Otherwise:

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

It wouldn't be an issue unless Move-Item supported Object[] as -destination parameter.

echo $destination returns:

PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\tmp\folder1
PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\tmp
PSChildName       : folder1
PSDrive           : C
PSProvider        : Microsoft.PowerShell.Core\FileSystem
PSIsContainer     : True
Name              : folder1
Parent            : tmp
Exists            : True
Root              : C:\
FullName          : C:\tmp\folder1
Extension         : 
CreationTime      : 13.08.2015 10:53:11
CreationTimeUtc   : 13.08.2015 7:53:11
LastAccessTime    : 13.08.2015 10:53:11
LastAccessTimeUtc : 13.08.2015 7:53:11
LastWriteTime     : 13.08.2015 10:53:11
LastWriteTimeUtc  : 13.08.2015 7:53:11
Attributes        : Directory, NotContentIndexed
BaseName          : folder1
Target            : 
LinkType          : 
Mode              : d-----

The only solution I found is not use the function:

$folderPath = "C:\tmp"

$FOLDER_NAME = "folder1"

$destination = "$folderPath\$FOLDER_NAME"

if(!(Test-Path $destination))
{
  New-Item -path $folderPath -name $FOLDER_NAME -itemtype Directory
}

Move-Item -path "C:\tmp\file1" -destination $destination

If folder1 doesn't exist:

    Каталог: C:\tmp


Mode                LastWriteTime         Length Name                                                                                                                    
----                -------------         ------ ----                                                                                                                    
d-----       13.08.2015     11:06                folder1                                                                                                                 

MemberType                 : TypeInfo
DeclaringType              : 
DeclaringMethod            : 
ReflectedType              : 
StructLayoutAttribute      : System.Runtime.InteropServices.StructLayoutAttribute
GUID                       : 296afbff-1b0b-3ff5-9d6c-4e7e599f8b57
Module                     : CommonLanguageRuntimeLibrary
Assembly                   : mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
TypeHandle                 : System.RuntimeTypeHandle
FullName                   : System.String
Namespace                  : System
AssemblyQualifiedName      : System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
BaseType                   : System.Object
TypeInitializer            : 
IsNested                   : False
Attributes                 : AutoLayout, AnsiClass, Class, Public, Sealed, Serializable, BeforeFieldInit
GenericParameterAttributes : 
IsVisible                  : True
IsNotPublic                : False
IsPublic                   : True
IsNestedPublic             : False
IsNestedPrivate            : False
IsNestedFamily             : False
IsNestedAssembly           : False
IsNestedFamANDAssem        : False
IsNestedFamORAssem         : False
IsAutoLayout               : True
IsLayoutSequential         : False
IsExplicitLayout           : False
IsClass                    : True
IsInterface                : False
IsValueType                : False
IsAbstract                 : False
IsSealed                   : True
IsEnum                     : False
IsSpecialName              : False
IsImport                   : False
IsSerializable             : True
IsAnsiClass                : True
IsUnicodeClass             : False
IsAutoClass                : False
IsArray                    : False
IsGenericType              : False
IsGenericTypeDefinition    : False
IsConstructedGenericType   : False
IsGenericParameter         : False
GenericParameterPosition   : 
ContainsGenericParameters  : False
IsByRef                    : False
IsPointer                  : False
IsPrimitive                : False
IsCOMObject                : False
HasElementType             : False
IsContextful               : False
IsMarshalByRef             : False
GenericTypeArguments       : {}
IsSecurityCritical         : False
IsSecuritySafeCritical     : False
IsSecurityTransparent      : True
UnderlyingSystemType       : System.String
Name                       : String
CustomAttributes           : {[System.SerializableAttribute()], [System.Reflection.DefaultMemberAttribute("Chars")], [System.Runtime.InteropServices.ComVisibleAttribute(
                         (Boolean)True)], [__DynamicallyInvokableAttribute()]}
MetadataToken              : 33554536

Otherwise:

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

Working example after Ansgar's recommendations:

$folderPath = "C:\tmp"

function GetDestination {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true)]
    [string]$Name,

    [Parameter(Mandatory=$true)]
    [string]$ParentFolder
  )

  $path = Join-Path $ParentFolder $Name

  if(Test-Path -LiteralPath $path) {
    Get-Item -LiteralPath $path
  } else {
    New-Item -Path $ParentFolder -Name $Name -ItemType Directory
  }
}

$FOLDER_NAME = "folder1"

$destination = GetDestination $FOLDER_NAME $folderPath

Move-Item -LiteralPath "C:\tmp\file1" -Destination $destination

Solution

  • New-Item returns the created object, and PowerShell functions return all non-captured output, not just the argument of the return keyword.

    In Windows PowerShell, the results of each statement are returned as output, even without a statement that contains the Return keyword. Languages like C or C# return only the value or values that are specified by the Return keyword.

    This means that if the path already exists your function returns just the path string. Otherwise it returns an array with the new DirectoryInfo object and the path string.

    Depending on whether you want a string or a DirectoryInfo object returned you can either suppress the output of New-Item:

    if (!(Test-Path $path)) {
      New-Item -Path $parentFolder -Name $name -ItemType Directory | Out-Null
    }
    
    return $path
    

    or remove the return statement and instead add an else branch where you call Get-Item on the path:

    if (!(Test-Path $path)) {
      New-Item -Path $parentFolder -Name $name -ItemType Directory
    } else {
      Get-Item $path
    }
    

    On a more general note, I'd recommend some modifications to your code:

    • Use the -LiteralPath parameter with Test-Path, so you don't run into problems when the path contains special characters like square brackets.
    • Use Join-Path for constructing a path, as that will automatically take care of path separators.
    • Use advanced parameter definitions, which will allow you to make parameters mandatory, define parameter order, validate input, and lots of other things.
    • Use the PowerShell naming convention with approved verbs for your function names.

    Example:

    function New-Folder {
      [CmdletBinding()]
      Param(
        [Parameter(Mandatory=$true)]
        [string]$Name,
        [Parameter(Mandatory=$true)]
        [string]$ParentFolder
      )
    
      $path = Join-Path $ParentFolder $Name
    
      if (-not (Test-Path -LiteralPath $path)) {
        New-Item -Path $ParentFolder -Name $Name -ItemType Directory
      } else {
        Get-Item -LiteralPath $path
      }
    }