如何通用化快捷方式?

如何通用化快捷方式?

我制作了一个游戏,并将所有代码和可执行文件放在code文件夹中。我在文件夹外创建了可执行文件的快捷方式,这样用户就不必在文件夹中查找 exe 文件。问题是快捷方式文件路径只对我有用。

我将项目上传到 GitHub,因此我的文件路径从下载文件夹开始。因此,对于我的快捷方式属性,我尝试

"%userprofile%\Downloads\Space-Invaders-main\Code\Space Invaders.exe"针对目标和

%userprofile%\Downloads\Space-Invaders-main\Code开始于。

我的朋友尝试过,但他得到了这个错误

我如何创建文件路径,以便任何计算机上的任何用户都可以运行快捷方式?

答案1

快捷方式不支持相对路径。确实就是这么简单。开发人员通常使用批处理文件 ( .bat) 来实现您所描述的功能。

然而,我发现了一个有趣的替代方法,使用 Windows 资源管理器作为中间件:

  1. 在“快捷方式”选项卡下,删除“启动位置”字段中的“C:\Windows”,使其空白。这确保快捷方式将在其当前路径中启动。
  2. 在目标字段中添加以下内容:%windir%\explorer.exe。在其后,用双引号添加程序的相对路径。例如,%windir%\explorer.exe "\your\folder\app.exe” 或 %windir%\explorer.exe "..\my\files"

来源

使用此方法您可能仍会遇到图标问题,但这是您可以做的最好的事情。

答案2

这是一个略有不同的问题,但解决方案可能有用。
我有大量 ePub(书籍)文件,按类型和作者姓名存储在树结构中。[例如 Q:\SFF\B\Bloggs、Fred\Space Wars.epub]。当作者使用多个笔名时,我会在作者文件夹中放置快捷方式,指向具有其他名称的备用文件夹。当我将整个库复制到便携式硬盘驱动器时,快捷方式失败。
解决方案是在源 PC 上创建一个环境变量“Z”,其值为原始驱动器路径 [在本例中为“Q:\”]。
然后右键单击快捷方式并选择“属性”。
在“目标”区域中,将驱动器 [在本例中为“Q:\”] 替换为“%Z%”并保存。[如果环境变量没有完成现有文件或文件夹的路径,则保存将不起作用。]
当便携式驱动器插入另一台 PC 时,可以为其分配任何驱动器号。将环境变量“Z”的值设置 [或更改] 为正确的驱动器号,然后所有修改后的快捷方式都将起作用。
我选择了“Z”,但环境变量可以有任何(未使用的)名称。“%Z%”的字符数与“Q:\”相同,我还没有编写一个程序来扫描我的库并将所有快捷方式修改为相对 - 这是下一个主要项目。

可以运行放置在便携式驱动器上的以下批处理文件“Z_Relative.bat”来设置环境变量。

    @echo off
    REM Batch file to set the Environment Variable "Z" to the current drive.
    REM Place in root directory of USB drive to setup relative shortcuts 
    REM     WITHIN the drive if run from there.

    IF NOT %Z%==%CD% SETX Z %CD%

(这是必需的,因为 %CD% 仅在命令提示符中起作用,而在快捷方式中不起作用。)

答案3

您可以制作“通用”/可移植/可共享的快捷方式,但它需要编辑 Windows 创建的二进制.lnk文件——com 对象和 IShellLink 接口都不会直接操作标志或选择性排除/包含数据。

首先下载一份文件[MS-SHLLINK].pdf规格表.lnk。请注意:

  • 除了标头额外数据(接线端子)是可选的,但一个有效的链接也必须始终有一个由免疫学环境道具小路。
  • 各种链接标志及其功能

但是当 Windows 创建快捷方式时,它总是会具有:

部分 元素
标头 LinkFlags、IconIndex、目标属性、RunAs、ShowWindow
身份列表 以 shell 命名空间为根的路径,即This PC\C:\WindowsLibraries\Documents
友情链接 VolumeID、UNC 路径等。
字符串数据 相对路径,工作目录
额外数据 TrackerProps、PropertyStore、EnvironemtProps(如果目标是使用环境变量指定的)KnownFolder/SpecialFolder(如果适用)

因此,特定于机器的数据存在于友情链接追踪器道具, 和物业商店部分。所有这些内容都可以删除,但链接仍然有效。

如果目标是使用环境变量指定的,则该路径将保存在环境道具部分,但身份列表将指定扩展的、特定于创建时间的位置——并且通常,当两者都存在时,优先考虑。对于环境属性路径优先,身份列表必须删除,HasLinkTargetIDList清除标志,并PreferEnvironmetPath设置标志。所有 Windows 机器都有一个内置示例——“命令提示符”快捷方式开始菜单.如果你查看它的属性对话框中,您会看到目标和“开始于”(工作目录)通过环境变量指定:

在此处输入图片描述

更重要的是,如果你解析链接标志(代码即将推出...),您会看到在任何用户创建的快捷方式上都不会看到的标志组合:

C:\\\\\\\\\System Tools> (Get-LinkFlags -Path '.\Command Prompt.lnk').LinkFlags

HasName
HasRelativePath
HasWorkingDir
HasIconLocation
IsUnicode
ForceNoLinkInfo
HasExpString
PreferEnvironmentPath

如果用户使用相同的变量创建快捷方式,则链接标志将:

HasLinkTargetIDList
HasLinkInfo
HasName
HasRelativePath
HasWorkingDir
IsUnicode
HasExpString
EnableTargetMetadata

并删除如下块身份列表友情链接,以及设置/清除相应的链接标志,我们必须破解.lnk文件中的字节。

因此,目标是否由环境属性或者身份列表,你总是想要:

  • 去除友情链接追踪器道具, 和PropStore道具块,以及已知文件夹/特殊文件夹如果存在的话,` 块。
  • 清除HasLinkInfoEnableTargetMetadata标志。
  • 设置ForceNoLinkInfoForceNoLinkTrackDisableKnownFolderTracking标志。

但首先,您需要创建一个快捷方式,这意味着确定最适合您目的的路径。环境属性路径最适合以下位置%应用程序数据%,可以通过新的快捷方式巫师或脚本com 对象。

但是对于像下载%用户资料\下载%如果用户重定向了下载文件夹。指定文件夹的更好方法是身份列表对应于:

  • 此电脑\下载

因为它即使对于重定向的下载文件夹也有效。

确保获得所需结果的最佳方式身份列表路径是导航到目标探索者,注意地址栏,然后使用上下文菜单Create shortcut命令。这将确保您不会最终得到身份列表指定此 PC\C:\Users\UserProfileFolder\Downloads. 因为属性对话框将显示两者相同的文件系统路径。

创建完成后可以修改描述窗口样式, 和运行方式通过特性对话框,但如果你想用环境变量指定图标位置,你必须使用 com 对象,或者像我通常做的那样,设置看似无用的相对路径$null,保存时也会清除相应的标志。

现在是时候进行字节操作、确定块偏移量和删除选定块的脏活了(幸运的是,这是一个减法而不是加法的练习)。为此,我写了以下内容电源外壳代码。

###   LinkFlags enum   ########################################################################################
Try {                               ###   Try/Catch to suppress TypeExists error when run more than once in the same session
    Add-Type -TypeDefinition @'
    using System;
    [FlagsAttribute]
    public enum LinkFlags : uint {
        HasLinkTargetIDList         = 0x00000001,               HasLinkInfo                 = 0x00000002,
        HasName                     = 0x00000004,               HasRelativePath             = 0x00000008,
        HasWorkingDir               = 0x00000010,               HasArguments                = 0x00000020,
        HasIconLocation             = 0x00000040,               IsUnicode                   = 0x00000080,
        ForceNoLinkInfo             = 0x00000100,               HasExpString                = 0x00000200,
        RunInSeparateProcess        = 0x00000400,               HasDarwinID                 = 0x00001000,
        RunAsUser                   = 0x00002000,               HasExpIcon                  = 0x00004000,
        NoPidlAlias                 = 0x00008000,               RunWithShimLayer            = 0x00020000,
        ForceNoLinkTrack            = 0x00040000,               EnableTargetMetadata        = 0x00080000,
        DisableLinkPathTracking     = 0x00100000,               DisableKnownFolderTracking  = 0x00200000,
        DisableKnownFolderAlias     = 0x00400000,               AllowLinkToLink             = 0x00800000,
        UnaliasOnSave               = 0x01000000,               PreferEnvironmentPath       = 0x02000000,
        KeepLocalIDListForUNCTarget = 0x04000000
    }
'@
} Catch {}

###   StringData LinkFlag/ValueName dictionary   #############################################################
#
$StringDataID = [Ordered]@{
    HasName                 = 'NameString';             HasRelativePath         = 'RelativePath'
    HasWorkingDir           = 'WorkingDir';             HasArguments            = 'CommandLineArguments'
    HasIconLocation         = 'IconLocation'
}

[String]$StringDataFlags            = $StringDataID.Keys -join '|'   ###   Regex to test for StringData   ####

###   ExtraData blockName/Signature dictionary   #############################################################
#
$ExtraDataSig = @{
    CONSOLE_PROPS                   = [UInt32]'0xA0000002';     CONSOLE_FE_PROPS        = [UInt32]'0xA0000004'
    DARWIN_PROPS                    = [UInt32]'0xA0000006';     ENVIRONMENT_PROPS       = [UInt32]'0xA0000001'
    ICON_ENVIRONMENT_PROPS          = [UInt32]'0xA0000007';     KNOWN_FOLDER_PROPS      = [UInt32]'0xA000000B'
    PROPERTY_STORE_PROPS            = [UInt32]'0xA0000009';     SHIM_PROPS              = [UInt32]'0xA0000008'
    SPECIAL_FOLDER_PROPS            = [UInt32]'0xA0000005';     TRACKER_PROPS           = [UInt32]'0xA0000003'
    VISTA_AND_ABOVE_IDLIST_PROPS    = [UInt32]'0xA000000C'
}

###   Constants and supporting objects   #####################################################################

$HeaderSize             = 0x4c
$FlagsOffset            = 0x14
$ASCIIEncoder           = [System.Text.Encoding]::GetEncoding('ASCII')
$UniEncoder             = [System.Text.Encoding]::GetEncoding('Unicode')

###   Utility functions   ####################################################################################

##############################################################################################################

Add-Type -AssemblyName System.Windows.Forms

Function Get-PathViaDialog {
    $OPenDlg = [System.WIndows.Forms.OpenFileDialog]::new()
    $OpenDlg.Title              = 'Select shortcut to modify'
    $OpenDlg.Filter             = 'Shortcut files (*.lnk)|*.lnk|All files (*.*)|*.*'
    $OpenDlg.DefaultExt         = '.lnk'
    $OpenDlg.DereferenceLinks   = $true
    $OpenDlg.InitialDirectory   = 'shell:Desktop'
    If ($OpenDlg.ShowDialog()) {
        $OpenDlg.FileName
    } Else {
        ''
}}

Function Save-ViaDialog {
    Param(
        [Byte[]]$ShLinkBytes,
        [String]$InitialDirectory
    )
    $SaveDlg       = [System.Windows.Forms.SaveFileDialog]::new()
    $SaveDlg.Title              = 'Save as...'
    $SaveDlg.Filter             = "Shortcut files (*.lnk)|*.lnk|All files (*.*)|*.*"
    $SaveDlg.AddExtension       = $true
    $SaveDlg.DefaultExt         = '.lnk'
    $SaveDlg.DereferenceLinks   = $true
    $SaveDlg.InitialDirectory   = $(If ($InitialDirectory) {$InitialDirectory} Else {'shell:Desktop'})
    If ($SaveDlg.ShowDialog())
    {
        [System.IO.File]::WriteAllBytes($SaveDlg.FileName , $ShLinkBytes)
        ###   Returnnvalue
        $SaveDlg.FileName
}}

###   Get-LinkFlags   ########################################################################################
#   Retrieve flags from .lnk file or Byte[]
#
Function Get-LinkFlags {
    param(
        [Alias('FullName')]
        [Parameter(Mandatory, Position=0, ParameterSetName='FromFile', ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String[]]$Path,
        [Parameter(Mandatory, Position=0, ParameterSetName='FromBytes')]
        [Byte[]]$LinkBytes
    )
    Process {
        If ($PSCmdlet.ParameterSetName -eq 'FromFile')
        {
            ForEach ($p in @($Path)) {
                @((Resolve-Path $p).Path) | ForEach {
                    $lnkFileInfo        = Get-item $_
                    [PSCustomObject]@{
                        'Name'          = $lnkFileInfo.Name
                        'LinkFlags'     = [LinkFlags]([BitConverter]::ToUInt32(([IO.File]::ReadAllBytes($_)), $FlagsOffset)) -split ', '
                        'FullName'      = $lnkFileInfo.FullName
        }}}}
        Else {
            [LinkFlags]([BitConverter]::ToUInt32($LinkBytes, $FlagsOffset))
}}}

###   Set-LinkFlags   ########################################################################################
#   Update the LinkFlags in a file or Byte[] by setting (-Raise) or clearing specified flags.
#
Function Set-LinkFlags  {
    param(
        [Parameter(Mandatory, Position=0, ParameterSetName='InByteArray')]
        [Byte[]]$ShLinkBytes,
        [Parameter(Mandatory, Position=0, ParameterSetName='InFile')]
        [String]$Path,
        [LinkFlags]$Raise,
        [LinkFlags]$Clear
    )
    Begin   {
        If ($PSCmdlet.ParameterSetName -eq 'InFile') {
            $ShLinkBytes = [System.IO.File]::ReadAllBytes($Path)
        }
        [LinkFlags]$currFlags = [LinkFlags]([BitConverter]::ToUInt32($ShLinkBytes, $FlagsOffset))
    }
    Process {
        [BitConverter]::GetBytes((($currFlags -bor $Raise -band (-bnot $Clear))).Value__) |
          ForEach -Begin { $i = $FlagsOffset } -Process {
              $ShLinkBytes[($i++)] = $_
          } -End {}
    }
    End     {
        If ($PSCmdlet.ParameterSetName -eq 'InFile') {
            [System.IO.File]::WriteAllBytes($Path, $ShLinkBytes)
}}}

###   Get-ExtraDataIndex   ######################################################################################
#   Use the regular expression engine to locate the signature bytes of
#   all ExtraData blocks present in the ShellLInk.
#   If found, return name, offset, and size of the block(s).
#
Function Get-ExtraDataIndex {
    Param(
        [Byte[]]$SHLinkBytes
    )
    $LinkBYtesAsString  = $ASCIIEncoder.GetString($SHLinkBytes)
    $BlockIndex         = $ExtraDataSig.Keys | ForEach {
        $BlockOffset    = [Regex]::new([Regex]::Escape($ASCIIEncoder.GetString([BitConverter]::GetBytes($ExtraDataSig[$_])))).Match($LinkBYtesAsString).Index - 4
        If ($BlockOffset -gt 0) {
            [PSCustomObject]@{
                'Name'     = $_
                'Offset'   = $BlockOffset
                'Size'     = [BitConverter]::ToUInt32($SHLinkBytes, $BlockOffset)
    }}}
    Write-Verbose ($BlockIndex | select Name, @{n='0xOffset';e={'0x{0:x4}' -f $_.Offset}}, @{n='0xSize';e={'0x{0:x4}' -f $_.Size}} | Out-String)
    $BlockIndex
}

###   New-RoamingShortcut   ####################################################################################
#   Create a modified copy of an existing shortcut with the LinkInfo and TrackerProps sections removed,
#   as well as KnownFolderProps and SpecialFolderProps if they exist.
#   Optionally:
#       - If the shortcut has an EnvironmentProps block, set the PreferEnvironmentPath flag and remove the IDList.
#       - Un-alias paths so file system path is always displalyed.
#       - Remove the PropertyStore and clear the EnableTargetMetadata flag.
#
Function New-RoamingShortcut {
    Param(
        [Parameter(Position=0)]
        [ValidateScript({(Test-Path $_ -PathType Leaf) -and ($_ -match '\.lnk$')})]
        [String]$SourcePath,
        [Parameter(Position=1)]
        [ValidateScript({Test-Path $_ -IsValid})]
        [String]$OutFile,
        [Switch]$PreferEnvironMentPath,
        [Switch]$UnAliasPath,
        [Switch]$NoMetadata
    )
    # Initialize variables to null.
    [UInt32]$IDListOffset   = [UInt32]$IDListSize =
        [UInt32]$LinkInfoOffset = [UInt32]$LinkInfoSize =
                                    [UInt32]$StringDataOffset= $null

    If ( ! $SourcePath ) {
        $SourcePath             = Get-PathViaDialog
    } Else {
        $SourcePath           = (Resolve-Path $SourcePath).Path
    }
    
    $LinkBytes                  = [System.IO.File]::ReadAllBytes($SourcePath)
    [LinkFlags]$LinkFlags       = Get-LinkFlags $LinkBytes
    Write-Verbose "LinkFlags :`n$(@(@($LinkFlags -split ', ').ForEach{'{1,14}{0}' -f $_,' ' }) -join "`n")"
    
    $currOffset   = $HeaderSize
    Switch -Regex   ($LinkFLags)
    {
        'HasLinkTargetIDList'
        {
            $IDListOffset       = $currOffset
            $IDListSize         = 2 + [BitConverter]::ToUInt16($LinkBytes, $currOffset)
            $currOffset        += $IDListSize
        }
        'HasLinkInfo'
        {
            $LinkInfoOffset     = $currOffset
            $LinkInfoSize       = [BitConverter]::ToUInt32($LinkBytes, $currOffset)
            $currOffset        += $LinkInfoSize
        }
        $StringDataFlags
        {
            $StringDataOffset   = $currOffset
        }
    }
    $ExtraDataIndex             = Get-ExtraDataIndex $LinkBytes | sort Offset
    If ($StringDataOffset) {
        $StringDataSize         = $ExtraDataIndex[0].Offset - $StringDataOffset
    }
    $ExcludedBlocks             = 'Special|Known|Tracker'
    If ($NoMetadata)  { $ExcludedBlocks += '|Property' }
    
    ###   Construct New ShellLink   ###
    
    If ($PreferEnvironmentPath -and ($LinkFlags -match 'HasExpString')) {
        $IDListSize             =   $null
    }
    [Byte[]]$NewLink       = [Byte[]](
                                    $LinkBytes[0..($HeaderSize-1)] +
                                    $(If ($IDListSize)     {$LinkBytes[$IDListOffset..($IDListSize-1+$IDListOffset)]} )     +
                                    $(If ($StringDataSize) {$LinkBytes[$StringDataOffset..($StringDataSize - 1 + $StringDataOffset)]} ) +
                                    $($ExtraDataIndex | ? Name -notMatch $ExcludedBlocks | ForEach {$LinkBytes[$_.Offset..($_.Size - 1 + $_.Offset)]}) +
                                    [Byte[]]::new(4)
    )
    Set-LinkFlags $NewLink -Raise 'ForceNoLinkInfo, ForceNoLinkTrack, DisableKnownFolderTracking' -Clear 'HasLinkInfo'
    If ($PreferEnvironmentPath -and ($LinkFlags -match 'HasExpString')) {
        Set-LinkFlags $NewLink -Raise 'PreferEnvironmentPath' -Clear 'HasLinktargetIDList'
    }
    If ($UnAliasPath) {
        Set-LinkFlags $NewLink -Raise 'NoPidlAlias,DisableKnownFolderAlias,UnaliasOnSave'
    }
    If ($NoMetadata) {
        Set-LinkFlags $NewLink -Clear 'EnableTargetMetaData'
    }
    Write-Verbose "New LinkFlags :`n$(@(@((Get-LinkFlags $NewLink) -split ', ').ForEach{'{1}{0}' -f $_, (' ' * 14)}) -join "`n")"
    If ($OutFile) {
        If (! [IO.Path]::IsPathRooted($OutFile)) {   ### Convert relative path to fully-qualified path
            $OutFile    = Join-Path (Resolve-Path (Split-Path $OutFile)).Path (Split-Path $OutFile -Leaf)
        }
        [System.IO.File]::WriteAllBytes($OutFIle , $NewLink)
    } Else {
        $OutFile = Save-ViaDialog -ShLinkBytes $NewLink -InitialDirectory (Split-Path $SourcePath)
    }
    If (Test-Path $OutFile) {
        Write-Verbose ('New shortcut saved to "{0}".' -f $OutFIle)
    }
}

...即将提供说明和示例 - 但要确保我不会丢失迄今为止的所有工作......

答案4

DOS 时代的一些功能在 Windows 10 中仍然存在,在新 Windows 中运行旧应用程序时,更新后仍能正常工作。因此,我挖出了我 5 岁的女儿在四分之一世纪前要求的太空入侵者,并尝试将其作为便携式应用程序运行。

这似乎效果不错。现代语法是 command /c "command",但%comspec% /r在本地文件夹中仍然可行。一个问题是屏幕强制 Windows 10 进入全屏 640 x 480,但 25 年后动作和音效仍然同样有趣。

在此处输入图片描述

现在答案

在这里我将其发送到远程映射驱动器(作为 zip 格式,可以是 USB 拇指驱动器或软盘或电子邮件),然后在这里将其解压缩。

我删除了我的本地副本以确保它不会被找到。

当我点击远程快捷方式时,答案是肯定的它仍可正常运行。

在此处输入图片描述

Target: %comspec% /r "Start " " ".\code\SpaceInvaders.exe"
start in: "%windir%\explorer.exe .\code"

请注意,即使是这样的快捷方式也会被 VirusTotal 标记为误报,因为它与某些漏洞的编写方式相匹配!!

因此我没有发布 24 年前编译的 EXE 版本。但是,如果您“相信我”并想看看它是如何工作的,这里有一个带有修改后的快捷方式的新捆绑版本。 https://github.com/GitHubRulesOK/MyNotes/raw/master/Hogwartz.zip 在此处输入图片描述

相关内容