25

I am writing a batch file that executes a Powershell script that at one point loops items with UNC paths as attributes and uses Get-ChildItem on those paths. In a minimal version, this is what is happening in my scripts:

Master.bat

powershell -ExecutionPolicy ByPass -File "Slave.ps1"

Slave.ps1

$foo = @{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"

$bar = @{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"

@( $foo, $bar ) | ForEach-Object {
    $item = Get-ChildItem $_.Path
    # Do things with item
}

The problem I'm running into is that when I run Master.bat, it fails at Get-ChildItem with an error along the lines of

get-childitem : Cannot find path '\\remote-server\foothing' because it does not exist.

However, it seems to work perfectly fine if I run the Slave.ps1 file directly using Powershell. Why might this be happening only when the Master.bat file is run?

Things I have tried

  • Prepending the UNC paths with FileSystem:: with providers http://powershell.org/wp/2014/02/20/powershell-gotcha-unc-paths-and-providers/
  • Making sure there are no strange characters in the actual paths
  • Using the -literalPath parameter instead of the plain -path parameter for Get-ChildItem
  • Running Get-ChildItem \\remote-server\foothing in PowerShell and succeeding to verify connection to the remote server
Loïc MICHEL
  • 24,935
  • 9
  • 74
  • 103
Jon Chan
  • 969
  • 2
  • 12
  • 22

3 Answers3

47

I have found this issue when running scripts referring to UNC paths - but the error only occurs when the root of the script is set to a non file system location. e.g. PS SQLSEVER\

So the following fails with the same error:

cd env:
$foo = @{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"

$bar = @{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"

@( $foo, $bar ) | ForEach-Object {
    $item = Get-ChildItem $_.Path
    # Do things with item
     Write-Host $item
}

So my resolution was to ensure that the PS prompt was returned to a file system location before executing this code. e.g.

cd env:
$foo = @{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"

$bar = @{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"

cd c: #THIS IS THE CRITICAL LINE
@( $foo, $bar ) | ForEach-Object {
    $item = Get-ChildItem $_.Path
    # Do things with item
     Write-Host $item
}

I hope this helps - I would be very happy with the bounty as this is my first answer on stack overflow. P.S. I forgot to add - the PS command prompt root may be set by auto loaded modules in the configuration of your machine. I would check with Get-Location to see if you are actually executng from a non FileSystem location.

Rory
  • 521
  • 5
  • 5
  • 2
    +1 You, sir, are a genius. I've spent bloody hours trying to figure out why this wasn't working for me!! Many thanks indeed. – Steve365 Jun 02 '15 at 10:53
  • 2
    Thank you, Thank you, Thank you!! I had the same Problem script was failing and kicking me back to PS SQLSEVER\. – Chris Jun 15 '15 at 19:26
  • 2
    +1 Very good, I do cd $pwd to not mess up other paths, but this was hard to find withouth your suggestion! – Rogier Jul 14 '15 at 13:02
  • 7 years later - your still helping!! – Joe B Jun 09 '21 at 20:42
13

Rory's answer provides an effective workaround, but there's a solution that doesn't require changing the current location to a FileSystem provider location first:

Prefix your UNC paths with FileSystem:: to ensure that they are recognized correctly, irrespective of the current location:

$foo = @{
   Name = "Foo"
   Path = "FileSystem::\\remote-server\foothing"
}

$bar = @{
   Name = "Bar"
   Path = "FileSystem::\\remote-server\barthing"
}

Alternatively, here is a tweak to Rory's answer to avoid changing the current location session-globally (to preserve whatever the current location is), using Push-Location and Pop-Location:

try {
  # Switch to the *filesystem provider's* current location, whatever it is.
  Push-Location (Get-Location -PSProvider FileSystem)

  # Process the paths.
  $foo, $bar | ForEach-Object {
      $item = Get-ChildItem $_.Path
      # Do things with item
  }
} finally {
   # Restore the previous location.
   Pop-Location
}

Optional background information

This excellent blog post explains the underlying problem (emphasis added):

PowerShell doesn't recognize [UNC paths] as "rooted" because they're not on a PSDrive; as such, whatever provider is associated with PowerShell's current location will attempt to handle them.

Adding prefix FileSystem:: unambiguously identifies the path as being a FileSystem provider path, irrespective of the provider underlying the current location.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    I've been struggling for a while, your solution worked perfectly to me. Even though when I checked the line before with [System.IO.Directory]::Exists($path), I was having errors with Get-ChildItem -Path $path Adding FileSystem:: fixed it. – kerzek Oct 01 '18 at 19:53
  • Glad to hear it, @kerzek. Yes, using `[System.IO.Directory]::Exists($path)` always works, because .NET _only_ knows about _filesystem_ paths, unlike PowerShell with its multiple drive providers. – mklement0 Oct 01 '18 at 19:58
-1

I read somewhere else about the Push-Location and Pop-Location commands to counter this kind of problem - I landed on your question while manually, step-by-step, testing a new routine where the script has push/pop, but I forgot to do them on my PS window. After checking @Rory's answer I noticed I was on PS SQLServer:\ instead of PS C:\ prompt.

So a way to use this on your "slave" script would be:

$foo = @{Name = "Foo"}
$foo.Path = "\\remote-server\foothing"

$bar = @{Name = "Bar"}
$bar.Path = "\\remote-server\barthing"

@( $foo, $bar ) | ForEach-Object {
    $item = Get-ChildItem $_.Path
    Push-Location
    # Do things with item
    Pop-Location
}

Thought of adding the Push/Pop before and after the # Do things because it seems that it's those things that change the location.

João Ciocca
  • 776
  • 1
  • 10
  • 23
  • While using `Push-Location` / `Pop-Location` to _temporarily_ switch to a filesystem location is a good idea, your solution won't work, because (a) your `Push-Location` command comes _after_ `Get-ChildItem`, which is where the error surfaces and (b), even if you changed the order, `Push-Location` without arguments just pushes the _current_ location onto the stack of remembered locations, it doesn't switch to a _filesystem_ location, which is what is needed here. – mklement0 Oct 01 '18 at 20:09