LoginSignup
4

More than 3 years have passed since last update.

マウントしたネットワークドライブのパスからUNCパスを取得する

Last updated at Posted at 2016-11-16

概要

マウントしたネットワークドライブ(割り当てられたネットワークドライブ)の情報は、WMI(Win32_MappedLogicalDisk)から取得できる。

Windows PowerShell では WMI 関連コマンドレットが用意されているため、そちらから情報を取得し、パスの判定・UNC パスへの変換を行う関数。

PowerShell 関数

using namespaceGet-CimInstanceを使用しているため、Windows PowerShell 5.1 以降用。

入力された文字列がネットワークドライブのパスのようだったら、UNCパスに変換して出力する関数。
コメントベースヘルプも設定しているのでそちらも参考に。

using namespace System.Collections.Generic
using namespace System.Management.Automation

function Resolve-MapNetworkDrivePathToUnc {
<#
.SYNOPSIS
割り当てられたネットワークドライブのパスをUNCパスに解決します。
.DESCRIPTION
Resolve-MapNetworkDrivePathToUnc 関数は、割り当てられたネットワークドライブのパスをUNCパスに解決します。
指定されたパスが相対パスの場合は、絶対パスに変換した上で解決が行われます。
すでにUNCパスの場合、あるいはローカルパスの場合は絶対パスを返します。
存在しているパスを指定する必要があります。
.INPUTS
System.String
.OUTPUTS
System.String
#>
    [CmdletBinding(
        DefaultParameterSetName = 'LiteralPath',
        HelpUri = 'https://qiita.com/nukie_53/items/673e9581624fc8f201bf'
    )]
    [OutputType([string])]
    param (
        # 解決するパスを指定します。LiteralPath の値は、入力した内容のまま使用されます。ワイルドカードとして解釈される文字はありません。
        [Parameter(
            Mandatory = $true, 
            Position = 0,
            ValueFromPipeline = $true, 
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'LiteralPath'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath', 'ProviderPath', 'FullName', 'LocalPath')]
        [string[]]$LiteralPath
        ,
        # 解決するパスを指定します。ワイルドカードを使用できます。
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Path'
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path
    )
    begin {
        # 割り当てられたネットワークドライブの一覧をDictionaryに格納。
        [Dictionary[string, string]]$driveUncDic = [Dictionary[string, string]]::new()

        # WMI 経由でドライブ情報を取得。
        # DeviceID が ドライブレター、ProviderName が 実際のパスとなる。
        [string]$Wql = 'SELECT DeviceID, ProviderName FROM Win32_MappedLogicalDisk'
        Get-CimInstance -Query $Wql | 
            ForEach-Object -Process {
                $driveUncDic.Add($_.DeviceID, $_.ProviderName)
                $_.Dispose()
            }
    }
    process {
        # Resolve-Path コマンドレットに、
        # LiteralPath が指定されたときには $LiteralPath、Path が指定されたときは $Path を渡したいため、スプラッティングを使用する。
        [hashtable]$resolvePathParam = @{
            $($PSCmdlet.ParameterSetName) = $PSBoundParameters[$PSCmdlet.ParameterSetName]
        }
        [PathInfo]$p = $null
        foreach ($p in Resolve-Path @resolvePathParam) {
            if ($p.Provider.Name -ne 'FileSystem') {
                # ファイル以外のものが指定された場合はエラーを出してスキップ。
                $PSCmdlet.WriteError([ErrorRecord]::new(
                    [System.ArgumentException]::new(('${0} には実際に存在するファイルのパスを指定してください。' -f $PSCmdlet.ParameterSetName)),
                    'InvalidArgument',
                    [ErrorCategory]::InvalidArgument,
                    $p.ProviderPath
                ))
                continue
            }

            [uri]$u = [uri]($p.ProviderPath)
            [string]$localPath = $u.LocalPath # この後よく使うので一旦変数に格納。

            if ($u.IsUnc) {
                # すでにUNCパスならそのままでOK。
                $PSCmdlet.WriteObject($localPath)
                continue
            }

            # continue の対象がわかりにくくになるため、foreach ではなく、ForEach-Object を使用。
            $driveUncDic.GetEnumerator() |
                ForEach-Object -Process {
                    # Resolve-Path によりパスの大文字小文字は正規化されているので、そのまま比較してOK。
                    if ($localPath.StartsWith($_.Key)) {
                        # パスの先頭がドライブレターなら、ドライブレター以降を切り出して実際のパスと結合。
                        [string]$resolvedPath = $_.Value + $localPath.Substring($_.Key.Length)
                        $PSCmdlet.WriteObject($resolvedPath)
                        continue
                    }
                }

            # ローカルのパス(C ドライブとか)
            $PSCmdlet.WriteObject($localPath)
        }
    }
}

使用例

概要

batファイルから上記関数を含むPSスクリプトを呼び出し、UNCパスに変換されたパスをクリップボードへ送る。

batファイル

Clip-UNCPath.bat
@PowerShell.exe -Sta -NoProfile -ExecutionPolicy RemoteSigned -File "%~dpn0.ps1" %* -Prefix "" -Suffix ""

ps1スクリプト

Clip-UNCPath.ps1
using namespace System.Collections.Generic
using namespace System.Management.Automation

using assembly  System.Windows.Forms
using namespace System.Windows.Forms

[CmdletBinding(PositionalBinding = $false)]
Param(
    [Parameter(Position = 0, ValueFromRemainingArguments = $true)]
    [string[]]$InputObject,
    [string]$Separator = [System.Environment]::NewLine,
    [string]$Prefix = '<"',
    [string]$Suffix = '">'
)

function Resolve-MapNetworkDrivePathToUnc {
<#
.SYNOPSIS
割り当てられたネットワークドライブのパスをUNCパスに解決します。
.DESCRIPTION
Resolve-MapNetworkDrivePathToUnc 関数は、割り当てられたネットワークドライブのパスをUNCパスに解決します。
指定されたパスが相対パスの場合は、絶対パスに変換した上で解決が行われます。
すでにUNCパスの場合、あるいはローカルパスの場合は絶対パスを返します。
存在しているパスを指定する必要があります。
.INPUTS
System.String
.OUTPUTS
System.String
#>
    [CmdletBinding(
        DefaultParameterSetName = 'LiteralPath',
        HelpUri = 'https://qiita.com/nukie_53/items/673e9581624fc8f201bf'
    )]
    [OutputType([string])]
    param (
        # 解決するパスを指定します。LiteralPath の値は、入力した内容のまま使用されます。ワイルドカードとして解釈される文字はありません。
        [Parameter(
            Mandatory = $true, 
            Position = 0,
            ValueFromPipeline = $true, 
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'LiteralPath'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath', 'ProviderPath', 'FullName', 'LocalPath')]
        [string[]]$LiteralPath
        ,
        # 解決するパスを指定します。ワイルドカードを使用できます。
        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Path'
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path
    )
    begin {
        # 割り当てられたネットワークドライブの一覧をDictionaryに格納。
        [Dictionary[string, string]]$driveUncDic = [Dictionary[string, string]]::new()

        # WMI 経由でドライブ情報を取得。
        # DeviceID が ドライブレター、ProviderName が 実際のパスとなる。
        [string]$Wql = 'SELECT DeviceID, ProviderName FROM Win32_MappedLogicalDisk'
        Get-CimInstance -Query $Wql | 
            ForEach-Object -Process {
                $driveUncDic.Add($_.DeviceID, $_.ProviderName)
                $_.Dispose()
            }
    }
    process {
        # Resolve-Path コマンドレットに、
        # LiteralPath が指定されたときには $LiteralPath、Path が指定されたときは $Path を渡したいため、スプラッティングを使用する。
        [hashtable]$resolvePathParam = @{
            $($PSCmdlet.ParameterSetName) = $PSBoundParameters[$PSCmdlet.ParameterSetName]
        }
        [PathInfo]$p = $null
        foreach ($p in Resolve-Path @resolvePathParam) {
            if ($p.Provider.Name -ne 'FileSystem') {
                # ファイル以外のものが指定された場合はエラーを出してスキップ。
                $PSCmdlet.WriteError([ErrorRecord]::new(
                    [System.ArgumentException]::new(('${0} には実際に存在するファイルのパスを指定してください。' -f $PSCmdlet.ParameterSetName)),
                    'InvalidArgument',
                    [ErrorCategory]::InvalidArgument,
                    $p.ProviderPath
                ))
                continue
            }

            [uri]$u = [uri]($p.ProviderPath)
            [string]$localPath = $u.LocalPath # この後よく使うので一旦変数に格納。

            if ($u.IsUnc) {
                # すでにUNCパスならそのままでOK。
                $PSCmdlet.WriteObject($localPath)
                continue
            }

            # continue の対象がわかりにくくになるため、foreach ではなく、ForEach-Object を使用。
            $driveUncDic.GetEnumerator() |
                ForEach-Object -Process {
                    # Resolve-Path によりパスの大文字小文字は正規化されているので、そのまま比較してOK。
                    if ($localPath.StartsWith($_.Key)) {
                        # パスの先頭がドライブレターなら、ドライブレター以降を切り出して実際のパスと結合。
                        [string]$resolvedPath = $_.Value + $localPath.Substring($_.Key.Length)
                        $PSCmdlet.WriteObject($resolvedPath)
                        continue
                    }
                }

            # ローカルのパス(C ドライブとか)
            $PSCmdlet.WriteObject($localPath)
        }
    }
}

[string[]]$resolvedPath = 
    $InputObject | Resolve-MapNetworkDrivePathToUnc

[string]$clipTxt =
    $Prefix +
    ($resolvedPath -join ($Suffix + $Separator + $Prefix)) +
    $Suffix

[Clipboard]::SetText($clipTxt) # Copy

参考ほか

他のアプローチによる取得

【備忘録】ネットワークドライブとUNCの変換と、それに伴うハック

最後の無駄なコメントの意味

Markdown のコードブロックの中で PowerShell で .NET の静的メソッドを呼び出すコードを書いたら表示されないのを回避するバッドノウハウ

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4