powershell - 如何在 PowerShell 中找到部分路径的潜在源环境变量?

标签 powershell replace path environment-variables path-manipulation

我想编写一个将常规路径转换为包含环境变量的路径的函数:

例如:

C:\Windows\SomePath

转换成:
%Windir%\SomePath

我将如何做到这一点,这可能吗?

这是我想要做的,但问题是,我需要检查字符串
所有可能的变量,有没有更自动的方法?这样就不需要 -replace 运算符
function Format-Path
{
    param (
        [parameter(Mandatory = $true)]
        [string] $FilePath
    )

    if (![System.String]::IsNullOrEmpty($FilePath))
    {
        # Strip away quotations and ending backslash
        $FilePath = $FilePath.Trim('"')
        $FilePath = $FilePath.TrimEnd('\\')
    }

$FilePath = $FilePath -replace "C:\\Windows", "%Windir%"
$FilePath = $FilePath -replace "C:\\ProgramFiles", "%ProgramFiles%"
$FilePath = $FilePath -replace "C:\\ProgramFiles (x86)", "%ProgramFiles (x86)%"
# ETC.. the list goes on..

return $FilePath
}

# test case
Format-Path '"C:\Windows\SomePath\"'

输出是:
%Windir%\SomePath

编辑:
无效的输入或错误的代码并不是真正的问题,因为最后 $Path可以很容易地检查:
Test-Path -Path ([System.Environment]::ExpandEnvironmentVariables($FilePath))

最佳答案

下面的代码是我对此的看法。路径和反斜杠操作有一些特殊性,所以我试图在评论中解释所有内容。

有一个关键点,那就是 无界字符串搜索,例如由 -replace 执行的搜索, -like , .Contains()等,并可能产生不良结果 当一个变量路径的值是另一个变量路径或目录路径的子字符串时。例如,给定 %ProgramFiles% ( C:\Program Files ) 和 %ProgramFiles(x86)% ( C:\Program Files (x86) ),路径 C:\Program Files (x86)\Test可以转化为%ProgramFiles% (x86)\Test而不是 %ProgramFiles(x86)%\Test如果 %ProgramFiles%碰巧之前测试过%ProgramFiles(x86)% .

解决办法是到仅将变量的路径与完整路径段进行比较 .也就是说,在路径 C:\Program Files (x86)\Test 的情况下,比较会是这样的......

  • 测试与原始路径的相等性 C:\Program Files (x86)\Test .没有变量匹配。
  • 测试与父路径的相等性 C:\Program Files (x86) . %ProgramFiles(x86)%火柴。没有进一步的祖先路径(即 C: )被测试。
  • %ProgramFiles%永远不会匹配,因为部分路径 C:\Program Files未测试。

  • 通过仅针对完整路径段进行测试,变量与候选路径的比较顺序无关紧要。

    New-Variable -Name 'VariablesToSubstitute' -Option Constant -Value @(
        # Hard-code system variables that contain machine-wide paths
        'CommonProgramFiles',
        'CommonProgramFiles(x86)',
        'ComSpec',
        'ProgramData',            # Alternatively: ALLUSERSPROFILE
        'ProgramFiles',
        'ProgramFiles(x86)',
        'SystemDrive'
        'SystemRoot'              # Alternatively: WinDir
    
        'MyDirectoryWithoutSlash' # Defined below
        'MyDirectoryWithSlash'    # Defined below
    );
    
    function Format-Path
    {
        param (
            [parameter(Mandatory = $true)]
            [string] $FilePath
        )
    
        if (![System.String]::IsNullOrEmpty($FilePath))
        {
            # Strip away quotations
            $FilePath = $FilePath.Trim('"')
            # Leave trailing slashes intact so variables with a trailing slash will match
            #$FilePath = $FilePath.TrimEnd('\')
        }
    
        # Initialize this once, but only after the test code has started
        if ($null -eq $script:pathVariables)
        {
            $script:pathVariables = $VariablesToSubstitute | ForEach-Object -Process {
                $path = [Environment]::GetEnvironmentVariable($_)
                if ($null -eq $path)
                {
                    Write-Warning -Message "The environment variable ""$_"" is not defined."
                }
                else
                {
                    return [PSCustomObject] @{
                        Name = $_
                        Path = $path
                    }
                }
            }
        }
    
        # Test against $FilePath and its ancestors until a match is found or the path is empty.
        # Only comparing with complete path segments prevents performing partial substitutions
        # (e.g. a path starting with %ProgramFiles(x86)% being substituted with %ProgramFiles%, 
        #       or "C:\Windows.old" being transformed to "%SystemRoot%.old")
        for ($filePathAncestorOrSelf = $FilePath;
            -not [String]::IsNullOrEmpty($filePathAncestorOrSelf);
            # Split-Path -Parent removes the trailing backslash on the result *unless* the result
            # is a drive root.  It'd be easier to normalize all paths without the backslash, but
            # Split-Path throws an error if the input path is a drive letter with no slash, so
            # normalize everything *with* the backslash and strip it off later.
            $filePathAncestorOrSelf = EnsureTrailingBackslash (
                # Protect against the case where $FilePath is a drive letter with no backslash
                # We have to do this here because we want our initial path above to be
                # exactly $FilePath, not (EnsureTrailingBackslash $FilePath).
                Split-Path -Path (EnsureTrailingBackslash $filePathAncestorOrSelf) -Parent
            )
        )
        {
            # Test against $filePathAncestorOrSelf with and without a trailing backslash
            foreach ($candidatePath in $filePathAncestorOrSelf, $filePathAncestorOrSelf.TrimEnd('\'))
            {
                foreach ($variable in $pathVariables)
                {
                    if ($candidatePath -ieq $variable.Path)
                    {
                        $variableBasePath = "%$($variable.Name)%"
                        # The rest of the path after the variable's path
                        $pathRelativeToVariable = $FilePath.Substring($variable.Path.Length)
    
                        # Join-Path appends a trailing backslash if the child path is empty - we don't want that
                        if ([String]::IsNullOrEmpty($pathRelativeToVariable))
                        {
                            return $variableBasePath
                        }
                        # Join-Path will join the base and relative path with a slash,
                        # which we don't want if the variable path already ends with a slash
                        elseif ($variable.Path -like '*\')
                        {
                            return $variableBasePath + $pathRelativeToVariable
                        }
                        else
                        {
                            return Join-Path -Path $variableBasePath -ChildPath $pathRelativeToVariable
                        }
                    }
                }
            }
        }
    
        return $FilePath
    }
    
    function EnsureTrailingBackslash([String] $path)
    {
        return $(
            # Keep an empty path unchanged so the for loop will terminate properly
            if ([String]::IsNullOrEmpty($path) -or $path.EndsWith('\')) {
                $path
            } else {
                "$path\"
            }
        )
    }
    

    使用此测试代码...

    $Env:MyDirectoryWithoutSlash = 'C:\My Directory'
    $Env:MyDirectoryWithSlash    = 'C:\My Directory\'
    
    @'
    X:
    X:\Windows
    X:\Windows\system32
    X:\Windows\system32\cmd.exe
    X:\Windows.old
    X:\Windows.old\system32
    X:\Windows.old\system32\cmd.exe
    X:\Program Files\Test
    X:\Program Files (x86)\Test
    X:\Program Files (it's a trap!)\Test
    X:\My Directory
    X:\My Directory\Test
    '@ -split "`r`n?" `
        | ForEach-Object -Process {
            # Test the path with the system drive letter
            $_ -replace 'X:', $Env:SystemDrive
    
            # Test the path with the non-system drive letter
            $_
        } | ForEach-Object -Process {
            $path = $_.TrimEnd('\')
    
            # Test the path without a trailing slash
            $path
    
            # If the path is a directory (determined by the
            # absence of an extension in the last segment)...
            if ([String]::IsNullOrEmpty([System.IO.Path]::GetExtension($path)))
            {
                # Test the path with a trailing slash
                "$path\"
            }
        } | ForEach-Object -Process {
            [PSCustomObject] @{
                InputPath  = $_
                OutputPath = Format-Path $_
            }
        }
    

    ......我得到了这个结果......
    InputPath                             OutputPath
    ---------                             ----------
    C:                                    %SystemDrive%
    C:\                                   %SystemDrive%\
    X:                                    X:
    X:\                                   X:\
    C:\Windows                            %SystemRoot%
    C:\Windows\                           %SystemRoot%\
    X:\Windows                            X:\Windows
    X:\Windows\                           X:\Windows\
    C:\Windows\system32                   %SystemRoot%\system32
    C:\Windows\system32\                  %SystemRoot%\system32\
    X:\Windows\system32                   X:\Windows\system32
    X:\Windows\system32\                  X:\Windows\system32\
    C:\Windows\system32\cmd.exe           %ComSpec%
    X:\Windows\system32\cmd.exe           X:\Windows\system32\cmd.exe
    C:\Windows.old                        %SystemDrive%\Windows.old
    X:\Windows.old                        X:\Windows.old
    C:\Windows.old\system32               %SystemDrive%\Windows.old\system32
    C:\Windows.old\system32\              %SystemDrive%\Windows.old\system32\
    X:\Windows.old\system32               X:\Windows.old\system32
    X:\Windows.old\system32\              X:\Windows.old\system32\
    C:\Windows.old\system32\cmd.exe       %SystemDrive%\Windows.old\system32\cmd.exe
    X:\Windows.old\system32\cmd.exe       X:\Windows.old\system32\cmd.exe
    C:\Program Files\Test                 %ProgramFiles%\Test
    C:\Program Files\Test\                %ProgramFiles%\Test\
    X:\Program Files\Test                 X:\Program Files\Test
    X:\Program Files\Test\                X:\Program Files\Test\
    C:\Program Files (x86)\Test           %ProgramFiles(x86)%\Test
    C:\Program Files (x86)\Test\          %ProgramFiles(x86)%\Test\
    X:\Program Files (x86)\Test           X:\Program Files (x86)\Test
    X:\Program Files (x86)\Test\          X:\Program Files (x86)\Test\
    C:\Program Files (it's a trap!)\Test  %SystemDrive%\Program Files (it's a trap!)\Test
    C:\Program Files (it's a trap!)\Test\ %SystemDrive%\Program Files (it's a trap!)\Test\
    X:\Program Files (it's a trap!)\Test  X:\Program Files (it's a trap!)\Test
    X:\Program Files (it's a trap!)\Test\ X:\Program Files (it's a trap!)\Test\
    C:\My Directory                       %MyDirectoryWithoutSlash%
    C:\My Directory\                      %MyDirectoryWithSlash%
    X:\My Directory                       X:\My Directory
    X:\My Directory\                      X:\My Directory\
    C:\My Directory\Test                  %MyDirectoryWithSlash%Test
    C:\My Directory\Test\                 %MyDirectoryWithSlash%Test\
    X:\My Directory\Test                  X:\My Directory\Test
    X:\My Directory\Test\                 X:\My Directory\Test\
    

    请注意,始终首先使用斜杠搜索候选祖先路径,然后不搜索。这意味着在不太可能的情况下,有两个变量路径的区别仅在于尾随斜杠的存在与否,带有尾随斜杠的变量将被匹配。因此,如上所示,C:\My Directory\Test会变成%MyDirectoryWithSlash%Test ,看起来有点奇怪。通过颠倒第一个 foreach 的顺序在函数中循环从...

    foreach ($candidatePath in $filePathAncestorOrSelf, $filePathAncestorOrSelf.TrimEnd('\'))
    

    ...到...

    foreach ($candidatePath in $filePathAncestorOrSelf.TrimEnd('\'), $filePathAncestorOrSelf)
    

    ...相关输出更改为...
    InputPath                             OutputPath
    ---------                             ----------
    ...                                   ...
    C:\My Directory\                      %MyDirectoryWithoutSlash%\
    ...                                   ...
    C:\My Directory\Test                  %MyDirectoryWithoutSlash%\Test
    C:\My Directory\Test\                 %MyDirectoryWithoutSlash%\Test\
    ...                                   ...
    

    关于powershell - 如何在 PowerShell 中找到部分路径的潜在源环境变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60087651/

    相关文章:

    csv - 为 CSV 数据创建可编辑的 powershell GUI

    powershell - 有没有办法将powershell字符串转换为哈希表?

    sharepoint - 完全限定文件名必须少于 260 个字符,但它是

    jQuery 1.4.4 错误 "q.replace is not a function"

    html - 我不明白绝对路径如何与本地/远程主机一起工作

    algorithm - 寻找最低成本路径的非有向图算法

    powershell - 使用 Powershell 将匹配的输出导出到 CSV 作为第 1 列和第 2 列

    PHP 替换和小写

    replace - 执行 MAKE 时文件中的字符串替换

    node.js - Nginx proxy_pass 和绝对路径