Search code examples
windowspowershellscriptingpowershell-4.0system-administration

Unique Volume ID path of a volume seemingly corrupted while tryig to create a new junction with New-Item; How to fix, bypass or work around?


I am writing a PowerShell script where-in I create a directory junction.

During the development of the script I ran the New-Item command, saw the junction entry was created without looking closer at it. It turns out that the link/junction is created and corrupted at the same time, or so it appears. Here's what I mean...

I want to create absolute junctions (as opposed to relative ones) such that the links are independant of the drive letters assigned. Therefore, I use volume IDs reported by mountvol and its PS counterpart Get-Volume with some minor PS gymnastics (still learning PS). The script needs a reference of the current assigned drive letter to obtain the volume ID.

(Get-Volume | Where-Object {$_.DriveLetter -eq 'c'}).Path

or

(Get-WmiObject win32_volume | Select-Object DriveLetter, DeviceID | Where-Object {$_.DriveLetter -eq 'c:' }).DeviceID

achieves the desired volume path (with subtle differences - notice the colon in one and lack there of in the other). Also the dot notation used is avaiable only PS version 3 and up, I beleive.

Microsoft documentation I can dig describes these as "DOS device paths"

I really do not know how much of this is "DOS" and how much of this is "Windows" and where the line is ever drawn. I also know that Windows has a disk reference schema with \DEVICE\HardDiskX... but PS documentation on device cmdlets is very dry (try get-help Get-NtDeviceProperty) where I thought device path for a volume would be a device property, so, anyway, I did not come across this notation very much other than where I already know it occurs (for eg. eventlogs and diskpart).

So, continuing ahead with what is available, having received the volumeID path via the above cmdlets, irrespective of how you run the New-Item cmdlet, piping, parantethicals or direct string supply of the volumeID path; the New-Item command seemingly corrupts the 'Target' or 'Value' from:

\\?\Volume{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}\Some_Path

to

\??\\\?\Volume{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}\Some_Path

Here are the various ways I invoke New-Item, all with the same result:

New-Item -ItemType "Junction" -Path "Path_and_name_of_junction" -Value "\\?\Volume{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}\Some_Path"

or paths and names in variables like so:

New-Item -ItemType "Junction" -Path $Path_and_name_of_junction -Value $TargetVolumeIDandPath

Junction is created and works fine when used like so:

New-Item -ItemType "Junction" -Path "C:\Junction\Name" -Value "N:\Target\Directory"

It also works fine if the above paths are stored in variables, or if they are supplied by PS cmdlet objects:

New-Item -ItemType "Junction" -Path $PathAndNameOfJunction -Value $TargetVolumeIDandPath

Not knowing what is going on, and why New-Item behaves this way, I tried the following (so far as I can remember - tried few things):

  • Because this is a small part of a script and there are other variables and processes involved so, I spent a lot of time stepping through the code top-down to see if other elements (eg. Join-Path) were corrupting the original volumeID path, but it turns out it was the very last step of this part the New-Item;
  • Thought I'll just use mklink, works fine normally in the command prompt, but it turns out that this is a built in command to the windows command shell and is not available outside the traditional windows console/shell/command prompt.
  • Ok, then just call cmd.exe from PS and run the mklink command as an argument, using Start-Process or PoweShell Start-Process with the -Arguments 'mklink /j Link_path_and_name Link_path_and_target_using_volumeID_Path' switch; Nope, just presents a blank black shell without any action with the supplied arguments
  • Cannot recall what other specific resolutions I tried, but I am stumped at this point, and ready to ask for some fresh set of eyes and thinking for a solution.

How else can I acheive a junction creation with or without New-Item, in PowerShell that will create an absolute junction link pointing to a volume and path in that volume independant of the Windows assigned drive letter using the above mentioned volumeID path that are available from both mountvol or Get-Volume, OR even \DEVICE\HARDDISKX\ style referencing? Any ideas and assistance in the right directions is really appreciated.

Thanks in Advance.

Edits: Also test Join-Path and Test-Path with the "DOS Device Paths":

  • Both Test-Path and Join-Path have no problem processing non-root paths
  • Test-Path, however, complains that the supplied "DOS Device Path" is not valid, i.e. the root of the volume specified by \\?\Volume{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}\
PS N:\> Join-Path -Path "path" -ChildPath "childpath"
path\childpath
PS N:\> Join-Path -Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\" -ChildPath "childpath"
\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\childpath
PS N:\> Join-Path -Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\" -ChildPath "Program Files"
\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\Program Files
PS N:\> Join-Path -Path $PathProgsVolUid -ChildPath "Program Files"
\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\Program Files
PS N:\> Test-Path (Join-Path -Path "path" -ChildPath "childpath")
False
PS N:\> Test-Path (Join-Path -Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\" -ChildPath "childpath")
False
PS N:\> Test-Path (Join-Path -Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\" -ChildPath "Program Files")
True
PS N:\> Test-Path (Join-Path -Path $PathProgsVolUid -ChildPath "Program Files")
True
PS N:\> Test-Path $PathProgsVolUid
Test-Path : Cannot retrieve the dynamic parameters for the cmdlet. Cannot process argument because the value of
argument "path" is not valid. Change the value of the "path" argument and run the operation again.
At line:1 char:1
+ Test-Path $PathProgsVolUid
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Test-Path], ParameterBindingException
    + FullyQualifiedErrorId : GetDynamicParametersException,Microsoft.PowerShell.Commands.TestPathCommand

PS N:\> Test-Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\"
Test-Path : Cannot retrieve the dynamic parameters for the cmdlet. Cannot process argument because the value of
argument "path" is not valid. Change the value of the "path" argument and run the operation again.
At line:1 char:1
+ Test-Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}\"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Test-Path], ParameterBindingException
    + FullyQualifiedErrorId : GetDynamicParametersException,Microsoft.PowerShell.Commands.TestPathCommand

PS N:\> Test-Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}"
Test-Path : Cannot retrieve the dynamic parameters for the cmdlet. Cannot process argument because the value of
argument "path" is not valid. Change the value of the "path" argument and run the operation again.
At line:1 char:1
+ Test-Path "\\?\Volume{2c8a5b49-09f5-44f9-92ce-d5e8d0c6bf42}"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Test-Path], ParameterBindingException
    + FullyQualifiedErrorId : GetDynamicParametersException,Microsoft.PowerShell.Commands.TestPathCommand


Solution

  • See: https://winaero.com/create-symbolic-link-windows-10-powershell/

    mklink is a built-in command in cmd.exe and not available directly in PowerShell. However, you can call it from PowerShell by using cmd /c mklink.

    Get-ChildItem D:\MainFolder-Copy -Directory | Foreach {Get-ChildItem (Join-Path C:\MainFolder $_.Name) -Directory} | Foreach {cmd /c mklink /j ($_.FullName -replace 'C:\\MainFolder','D:MainFolder-Copy') $_.FullName}
    

    Or in PowerShell you can use New-Item

    New-Item -ItemType Junction -Path "Link" -Target "Target"
    

    Access Volume Shadow Copy (VSS) snapshots from PowerShell and create a junction using the volume letter specifier: Accessing Volume Shadow Copy (VSS) Snapshots from powershell

    $s1 = (Get-WmiObject -List Win32_ShadowCopy).Create ("C:\", "ClientAccessible")
    $s2 = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $s1.ShadowID }
    $d = $s2.DeviceObject + "\"
    cmd /c mklink /d C:shadowcopy "$d"