PSJunction.psm1
<#
.DESCRIPTION A module to get the current target of a junction point, set a new target, create a new junction point or to find junction points in any given path. .NOTES Version: 1.0.5 Author: Mattias Cedervall Creation Date: 2021-06-22 Purpose/Change: Fixed Find-JunctionPoints not to include loops #> Import-Module PSJunction.psd1 function New-JunctionPoint { <# .DESCRIPTION Creates a junction point without using any .exe nor PS-command to do so. I am aware of "New-item -Path 'Path' -Value 'Target' -ItemType Junction" Just wanted to do this in .NET. And besides, this function is a tiny bit better... C:\>dir testjunc* Volume in drive C is OSDisk Volume Serial Number is 2EC7-BCAE Directory of C:\ 2021-06-22 20:59 <JUNCTION> testJuncPS [\??\C:\temp] 2021-06-22 21:08 <JUNCTION> testjunc_New-JunctionPoint [c:\temp] New-item doesn't set the PrintName, this does. .PARAMETER Path The path of the junction point. .PARAMETER Target The target of the junction point. .NOTES Version: 1.0.5 Author: Mattias Cedervall Creation Date: 2021-06-22 Purpose/Change: Edited the description. .EXAMPLE New-JunctionPoint -Path C:\MyJunction -Target C:\temp #> param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string]$Target ) if ([System.IO.Directory]::Exists($Path)) { $CurrentJunctionPoint=(Get-Item $Path -Force -ErrorAction SilentlyContinue) if ($CurrentJunctionPoint -ne $null) { $ret=-4 PrintError -err $ret } } $junction=[System.IO.Junction]::new() $ret=$junction.CreateJunction($Path,$Target) if ($ret -eq 1) { $CurrentTarget=$junction.GetTarget($Path) if ($CurrentTarget.Target -like ("\??\"+$Target).TrimEnd("\").ToLower()) { return $true } } PrintError $ret return $ret } Function Set-JunctionPoint { <# .DESCRIPTION Can set (change) the target of a junction point instead of needing to delete it and create a new one. Still needing privileges to do so of course. With this function you don't need to keep the acl in mind which you do need to if you decide to delete and recreate a junction. .PARAMETER Path The path of the junction point. .PARAMETER NewTarget The new target of the junction point. .NOTES Version: 1.0.5Alpha Author: Mattias Cedervall Creation Date: 2021-06-22 Purpose/Change: Fixed description .EXAMPLE Set-JunctionPoint -Path C:\program -NewTarget C:\temp #> param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string]$NewTarget ) if ([System.IO.Directory]::Exists($Path)) { $CurrentJunctionPoint=Get-JunctionPoint -Path $Path if ($CurrentJunctionPoint.IsReparsePoint -ne $true) { $ret=-3 PrintError -err $ret } } $junction=[System.IO.Junction]::new() $ret=$junction.ChangeTarget($Path,$NewTarget) if ($ret -eq 1) { $CurrentTarget=$junction.GetTarget($Path) if ($CurrentTarget.Target -like ("\??\"+$NewTarget).TrimEnd("\").ToLower()) { return $true } } PrintError -err $ret return $ret } Function Get-JunctionPoint { <# .DESCRIPTION 20H2 bug with the Refresh scenario + non UEFI causes all default junctions to point to D:\ after the OSD is done if the OSDisk is D:\ in WinPE. While trying to detect how many computers that were affected by this I used "(Get-Item C:\Program -force).Target" but the Target was blank no matter if it was run by an User, an Admin or as "Local System". PS C:\WINDOWS\system32> whoami nt instans\system PS C:\WINDOWS\system32> (Get-Item C:\Program -force) | fl Name : Program CreationTime : 2017-12-12 00:25:58 LastWriteTime : 2017-12-12 00:25:58 LastAccessTime : 2017-12-12 00:25:58 Mode : d--hsl LinkType : Target : This function can get the target even while running as a normal user. PS D:\PSJunction> Get-JunctionPoint C:\program Path : c:\program Target : \??\c:\program files PrintName : C:\Program Files IsReparsePoint : True TagType : IO_REPARSE_TAG_MOUNT_POINT Err : 0 ErrMsg : .PARAMETER Path The path of the junction point .NOTES Version: 1.0Alpha Author: Mattias Cedervall Creation Date: 2021-06-11 Purpose/Change: Initial script development .EXAMPLE Get-JunctionPoint -Path C:\program #> param( [Parameter(Mandatory = $true)] [string]$Path ) $junction=[System.IO.Junction]::new() $ret=$junction.GetTarget($Path) return $ret } Function Find-JunctionPoints { <# .DESCRIPTION Tries to get all the juntions points within the given path and skipping paths that point to eachother causing loops. Also tries to find the target and the replace it in the output if .Target is null from Get-(child)Item. .PARAMETER Path The path of the directory to search. .PARAMETER Recurse Tries to find all junction points recursive without causing loops. .PARAMETER AutoCorrectTarget Sets the output to display (Get-JunctionPoint).PrintName as the target if the target from Get-item is $null. .NOTES Version: 1.0.4Alpha Author: Mattias Cedervall Creation Date: 2021-06-17 Purpose/Change: Initial script development .EXAMPLE Find-JunctionPoints -Path C:\programdata -Recurse #> param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $false)] [switch]$Recurse, [Parameter(Mandatory = $false)] [bool]$AutoCorrectTarget=$true ) [Array]$RPFromPath=(Get-Item -Path $Path -Force -ErrorAction SilentlyContinue | where {$_.attributes -match "ReparsePoint"}) if ($Recurse) { #[Array]$RPs=(Get-ChildItem $Path\* -Attributes ReparsePoint -Force -Directory -Recurse -ErrorAction SilentlyContinue) [Array]$RPs=(Get-ChildItem $Path\* -Attributes ReparsePoint -Force -Directory -ErrorAction SilentlyContinue) } else { [Array]$RPs=(Get-ChildItem $Path\* -Attributes ReparsePoint -Force -Directory -ErrorAction SilentlyContinue) } if ($RPs.Count -gt 0) { $Result=foreach ($RP in $RPs.GetEnumerator()) { ($RP.Name) if ($Recurse) { [Array]$JunctionsInRootJunctions=foreach ($RP in $RPs.GetEnumerator()) { Get-ChildItem $RP -Attributes ReparsePoint -Force -Directory -ErrorAction SilentlyContinue } } $Targets=$RPs.GetEnumerator().Target } } <# 2021-06-07 if ($RPs.Count -gt 0) { $Result=foreach ($RP in $RPs.GetEnumerator()) { ($RP.Name) } if ($Recurse) { [Array]$JunctionsInRootJunctions=foreach ($RP in $RPs.GetEnumerator()) { Get-ChildItem $RP -Attributes ReparsePoint -Force -Directory -ErrorAction SilentlyContinue } } $Targets=$RPs.GetEnumerator().Target } #> else { $Result="" } if ($Recurse) { #if ($Path.EndsWith(":\")) if ($Path.EndsWith("\*") -ne $true) { ##Bug within include/exclude if Path is a root dir,e.g. "C:\" $Path=$Path.TrimEnd('\') $Path=$Path+"\*" } $RPsRecursive=(Get-ChildItem $Path -Exclude $Result -Directory -ErrorAction SilentlyContinue -Force) | Get-ChildItem -Recurse -Attributes ReparsePoint -Force -ErrorAction SilentlyContinue -Directory #$RPsRecursive=(Get-ChildItem $Path -Exclude $Result -Directory -ErrorAction SilentlyContinue -Force -Attributes ReparsePoint -Recurse) } if ($JunctionsInRootJunctions.Count -gt 0) { $JunctionsInRootJunctions=$JunctionsInRootJunctions.GetEnumerator() | Where {$_.Target -notin $Targets} } #[Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods]::GetLinkType($psobject 'get-item') $AllRPs=$RPFromPath + $RPs + $RPsRecursive + $JunctionsInRootJunctions $AllRPs=foreach ($RPFound in $AllRPs) { if ($RPFound.FullName -notin ($null,"")) { $CurJunc=(Get-JunctionPoint -Path $($RPFound.FullName) -ErrorAction SilentlyContinue) $IsJunction=$CurJunc -ne $null if ($IsJunction -eq $true) { $RPFound } } } $ret=$($AllRPs | where {$_.LinkType -ne "SymbolicLink"}) if ($AutoCorrectTarget -eq $true) { $ret= [psobject[]]($ret| Select-Object *) foreach ($R in $ret) { if ($R.Target -eq $null) { $CurJunc=(Get-JunctionPoint -Path $($R.FullName) -ErrorAction SilentlyContinue) $R.Target=$($CurJunc.PrintName) } } } return [psobject[]]($ret| Select-Object *) } Function PrintError([int]$err) { if($err -eq -1) { $ret=[System.IO.IOException]::new("The junction path is not valid.",$err) Throw $ret } if ($err -eq -2) { $ret=[System.IO.IOException]::new("Target does not exist or is not a valid target.",$err) Throw $ret } if ($err -eq -3) { $ret=[System.IO.IOException]::new("The path is not a junction point.",$err) Throw $ret } if ($err -eq -4) { $ret=[System.IO.IOException]::new("The path already exists.",$err) Throw $ret } #Write-host "Error $err" } Export-ModuleMember -Function Get-JunctionPoint, Set-JunctionPoint, New-JunctionPoint, Find-JunctionPoints |