1

I've written a script to create a series of symbolic links. I want to set the target value to $shortpath where

$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"

The value of the $shortpath variable is valid and I can open it from the run command. The string that PS is trying to write at the creation of the symlink is different than anticipated. I expect that it would write the value of the string, or at least insert the value of the Env Variable. rather it is adding to the string I pass to it.

New-Item -Path $currpath -ItemType SymbolicLink -Value ($targetpath) -Force

I would expect a target value to be: c:\Users\UserName\dir1\dir2\dir3\filename.ext or %userprofile%\dir1\dir2\dir3\filename.ext

Instead, I am getting: C:\windows\system32%UserProfile$\dir1\dir2\dir3\filename.ext

example of output written to logfile:

wholepath = C:\Users\UserName\dir1\dir2\dir3\longfilename1.ext
spath = C:\Users\UserName\dir1\dir2\dir3\longfi~1.ext
envpath = C:\Users\UserName\
midpart = dir1\dir2\dir3\
filename = longfi~1.ext
targetpath = %UserProfile%\dir1\dir2\dir3\longfi~1.ext

Could anyone shed some light as to why this may be happening? The same thing is happening if i user mklink. I've added the entire script below:


function Get-ShortPathName
{
    Param([string] $path)

    $MethodDefinition = @'
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetShortPathNameW", SetLastError = true)]
public static extern int GetShortPathName(string pathName, System.Text.StringBuilder shortName, int cbShortName);
'@

    $Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru
    $shortPath = New-Object System.Text.StringBuilder(500)
    $retVal = $Kernel32::GetShortPathName($path, $shortPath, $shortPath.Capacity)
    return $shortPath.ToString()

    }

    $logfile="C:\SwSetup\SymLinkScript\log.txt"
    
    <#paths to orignials and place to copy to#>
    $src = $env:userprofile + "\Firestone Technical Resources, Inc\Firestone Technical Resources, Inc Team Site - Documents\Danielle"
    $dest = "C:\SwSetup\asdfg\"
    $src = $src.Replace("\","\\")


    <#  grab the root object, and its children, from the src#>
    $i = Get-ChildItem -LiteralPath $src -Recurse

    <# recurse through the root and process, for lack of a better term, each object#>
    $i | ForEach-Object { 
        Process {
            $apath = $_.FullName -Replace $src,""
            $cpath = $dest + $apath

            <# Create Directory if it does not exist#>
            If(!(Test-Path (Split-Path -Parent $cpath))){
                New-Item -ItemType Directory -Force -Path (Split-Path -Parent $cpath)
            }
            
            <#
            Create the SymLink if it does not exist
            mklink syntax         | PowerShell equivalent       
            mklink /D Link Target | new-item -path <path to location> -itemtype symboliclink -name <the name> -value <path to target>
            #>
            If(!$_.PSIsContainer){
                If(!(Get-Item $cpath -ErrorAction SilentlyContinue)){
                    <#establish 8.3path#>
                    $wholepath = ([WildcardPattern]::Escape($_.FullName))
                    $shortPath = Get-ShortPathName($wholepath)
                    $envpath = $shortpath.substring(0,18)
                    $midpart = ((Split-path $shortpath -parent).trimstart($envpath)) +"\"
                    $filename = Split-Path $shortpath -leaf
                    $targetpath = "%UserProfile%\" + $midpart + $filename
                    
                    <#write to log file#>
                    "wholepath = " + $wholepath >> $logfile
                    "spath = " + $Shortpath >>$logfile
                    "envpath = " + $envpath >> $logfile
                    "midpart = " +$midpart >>$logfile
                    "filename = " + $filename >> $logfile
                    "targetpath = " + $targetpath >> $logfile
                    "cpath = " + [string]$cpath >> $logfile
                    "----------" >>$logfile
                    " " >>$logfile
                    
                    <#create symlink#>
                    New-Item -Path $cpath -ItemType SymbolicLink -Value ($targetpath) -Force

                    <#cmd /c mklink $cpath $targetpath#>

                    <#create shortcut
                    $WshShell = New-Object -comObject WScript.Shell
                    $Shortcut = $WshShell.CreateShortcut($targetpath.substring(0,$targetpath.Length-4) + '.lnk')
                    $Shortcut.TargetPath = $targetpath
                    $Shortcut.Save()#>
                }
            }
        }
    }
mklement0
  • 382,024
  • 64
  • 607
  • 775
Mlesko
  • 21
  • 2

2 Answers2

0

First you have set a variable called $shortpath:

$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"

and then you say:

New-Item -Path $currpath -ItemType SymbolicLink -Value ($targetpath) -Force

I would expect a target value to be: c:\Users\UserName\dir1\dir2\dir3\filename.ext or %userprofile%\dir1\dir2\dir3\filename.ext

The reason your expectation is not met is that your New-Item line doesn't refer to your $shortpath variable.

Reg Edit
  • 6,719
  • 1
  • 35
  • 46
  • $shortpath is the 8.3 style of the filepath. it is used to create the $targetpath that is given to the new-item call. at time of execution the new-item call parses the %userprofile% env variable. My desired outcome is for the target of the symlink to be the string "%userprofile%\dir1\dir2\filename.ext" rather than "c:\users\USERNAME\dir1\dir2\filename.ext" when passed the literal string it is prepending the path the script is run from to it. EG c:\windows\sys32\%userProfile%\dir1\etc. – Mlesko Mar 01 '22 at 16:15
  • The variable-name confusion is unfortunate, but the real problem is that symbolic links do not support `cmd.exe`-style environment-variable references in their target paths - they only support _literal_ paths. – mklement0 Mar 01 '22 at 22:53
0
  • Shortcut files (.lnk) do support cmd.exe-style environment variable references (e.g. %USERPROFILE%) in their properties, but there's a caveat:

    • When a shortcut file is created or modified with WshShell COM object's .CreateShortcut() API:

      • Assigning property values, say, .TargetPath = '%USERPROFILE%\Desktop' works as expected - that is, the string is stored as-is and the reference to environment variable %USERPROFILE% is only expanded at runtime, i.e. when the shortcut file is opened.

      • However, querying such properties via this COM API also performs expansion, so that you won't be able to get raw definitions, and won't be able to distinguish between the .TargetPath property containing %USERPROFILE%\Desktop and, say, verbatim C:\Users\jdoe\Desktop

    • When in doubt about a given .lnk file's actual property values, inspect it via File Explorer.

  • Symbolic links (symlinks) do not.

The target of a symbolic link must be specified as a literal path, which in your case means using PowerShell's environment-variable syntax (e.g., $env:UserProfile) in order to resolve the variable reference to its value up front:

# This results in a *literal* path, because $env:UserProfile is
# instantly resolved; the result can be used with New-Item / mklink
$literalTargetPath = "$env:UserProfile\" + $midpart + $filename

Passing something like '%USERPROFILE%\Desktop' to the -Value / -Target parameter of New-Item -Type SymbolicLink therefore does not work (as intended), but the symptom depends on which PowerShell edition you're running:

  • Windows PowerShell, which you're using, tries to verify the existence of the target path up front, and therefore fails to create the symlink, because it interprets %USERPROFILE%\Desktop verbatim, as a relative path (relative to the current directory, which is why the latter's path is prepended), which doesn't exist.

  • PowerShell (Core) 7+ also interprets the path verbatim, but it allows creating symlinks with not-yet-existing targets, so the symlink creation itself succeeds. However, trying to use the resulting symlink then fails, because its target doesn't exist.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • I am able to, using explorer, bring up the properties of a symbolic link and change its target to use the %userprofile% environment variable. I can't however seem to find a way to do this programatticaly. – Mlesko Mar 01 '22 at 16:18
  • @Mlesko, you cannot modify the properties of a _symlink_ in File Explorer, only those of a _shortcut file_ - and only the latter support environment-variable references, as noted in the answer. See [this answer](https://stackoverflow.com/a/54732814/45375) for a discussion of their differences. – mklement0 Mar 01 '22 at 16:37