在 powershell 中使用 %userProfile% 环境变量创建路径时的意外行为
unexpected behavior when creating a path with %userProfile% environment variable in pwershell
我编写了一个脚本来创建一系列符号链接。我想将目标值设置为 $shortpath where
$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"
$shortpath 变量的值有效,我可以从 运行 命令打开它。 PS 在创建符号链接时尝试写入的字符串与预期的不同。我希望它会写入字符串的值,或者至少插入 Env 变量的值。相反,它正在添加到我传递给它的字符串中。
New-Item -Path $currpath -ItemType SymbolicLink -Value ($targetpath) -Force
我希望目标值为:c:\Users\UserName\dir1\dir2\dir3\filename.ext 或 %userprofile%\dir1\dir2\dir3\filename.ext
相反,我得到:C:\windows\system32%UserProfile$\dir1\dir2\dir3\filename.ext
写入日志文件的输出示例:
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
谁能解释一下为什么会发生这种情况?如果我使用 mklink,也会发生同样的事情。我在下面添加了整个脚本:
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()#>
}
}
}
}
首先你设置了一个名为$shortpath
的变量:
$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"
然后你说:
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
未达到您的期望的原因是您的 New-Item
行没有引用您的 $shortpath
变量。
快捷方式文件 (.lnk
) do在它们的属性中支持 cmd.exe
风格的环境变量引用(例如 %USERPROFILE%
),但是有一个 警告 :
使用 WshShell COM 对象的 .CreateShortcut()
创建或修改快捷方式文件时 API:
分配 属性值,比如说,.TargetPath = '%USERPROFILE%\Desktop'
按预期工作——也就是说,字符串存储在as-is 并且对环境变量 %USERPROFILE%
的引用仅在运行时 扩展,即当快捷方式文件 打开 .
但是,查询这样的属性通过这个COM API 也执行扩展,这样你将无法获得 raw 定义,并且无法区分包含 %USERPROFILE%\Desktop
的 .TargetPath
属性 和,比如说, 逐字记录C:\Users\jdoe\Desktop
当对给定 .lnk
文件的实际 属性 值有疑问时,请通过 文件资源管理器.[=29= 检查它]
符号links (symlinks) 做不是.
符号 link 的目标必须指定为 文字 路径 ,在您的情况下这意味着使用 PowerShell's environment-variable syntax(例如,$env:UserProfile
)以便将变量引用解析为其值 预先:
# 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
将 '%USERPROFILE%\Desktop'
之类的东西传递给 New-Item -Type SymbolicLink
的 -Value
/ -Target
参数因此 不会 起作用(按预期),但症状取决于您使用的 PowerShell 版本 运行:
您正在使用的 Windows PowerShell 会尝试预先验证目标路径的存在,因此 无法创建 symlink,因为它将 %USERPROFILE%\Desktop
verbatim 解释为 relative ] 路径(相对于当前目录,这就是为什么后者的路径是前置的),它不存在。
PowerShell (Core) 7+ 也解释路径 verbatim,但它允许创建 symlinks 与 not-yet-existing 目标,因此 symlink 创建本身成功。但是,尝试 使用 结果 symlink 失败,因为它的目标不存在。
我编写了一个脚本来创建一系列符号链接。我想将目标值设置为 $shortpath where
$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"
$shortpath 变量的值有效,我可以从 运行 命令打开它。 PS 在创建符号链接时尝试写入的字符串与预期的不同。我希望它会写入字符串的值,或者至少插入 Env 变量的值。相反,它正在添加到我传递给它的字符串中。
New-Item -Path $currpath -ItemType SymbolicLink -Value ($targetpath) -Force
我希望目标值为:c:\Users\UserName\dir1\dir2\dir3\filename.ext 或 %userprofile%\dir1\dir2\dir3\filename.ext
相反,我得到:C:\windows\system32%UserProfile$\dir1\dir2\dir3\filename.ext
写入日志文件的输出示例:
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
谁能解释一下为什么会发生这种情况?如果我使用 mklink,也会发生同样的事情。我在下面添加了整个脚本:
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()#>
}
}
}
}
首先你设置了一个名为$shortpath
的变量:
$shortpath = "%userprofile%\dir1\dir2\dir3\filename.ext"
然后你说:
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
未达到您的期望的原因是您的 New-Item
行没有引用您的 $shortpath
变量。
快捷方式文件 (
.lnk
) do在它们的属性中支持cmd.exe
风格的环境变量引用(例如%USERPROFILE%
),但是有一个 警告 :使用 WshShell COM 对象的
.CreateShortcut()
创建或修改快捷方式文件时 API:分配 属性值,比如说,
.TargetPath = '%USERPROFILE%\Desktop'
按预期工作——也就是说,字符串存储在as-is 并且对环境变量%USERPROFILE%
的引用仅在运行时 扩展,即当快捷方式文件 打开 .但是,查询这样的属性通过这个COM API 也执行扩展,这样你将无法获得 raw 定义,并且无法区分包含
%USERPROFILE%\Desktop
的.TargetPath
属性 和,比如说, 逐字记录C:\Users\jdoe\Desktop
当对给定
.lnk
文件的实际 属性 值有疑问时,请通过 文件资源管理器.[=29= 检查它]
符号links (symlinks) 做不是.
符号 link 的目标必须指定为 文字 路径 ,在您的情况下这意味着使用 PowerShell's environment-variable syntax(例如,$env:UserProfile
)以便将变量引用解析为其值 预先:
# 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
将 '%USERPROFILE%\Desktop'
之类的东西传递给 New-Item -Type SymbolicLink
的 -Value
/ -Target
参数因此 不会 起作用(按预期),但症状取决于您使用的 PowerShell 版本 运行:
-
您正在使用的
Windows PowerShell 会尝试预先验证目标路径的存在,因此 无法创建 symlink,因为它将
%USERPROFILE%\Desktop
verbatim 解释为 relative ] 路径(相对于当前目录,这就是为什么后者的路径是前置的),它不存在。PowerShell (Core) 7+ 也解释路径 verbatim,但它允许创建 symlinks 与 not-yet-existing 目标,因此 symlink 创建本身成功。但是,尝试 使用 结果 symlink 失败,因为它的目标不存在。