しばたテックブログ

気分で書いている技術ブログです。

PowerShellでmountvolコマンドを代用する

ディスクボリュームのマウント・アンマウントやドライブレターの割り当てを行うのに便利なmountvolコマンドですが、本エントリではこれをPowerShellで代用する方法について説明します。

Storage Cmdletの使用

Windows 8/Windows Server 2012以降であれば、Storage Cmdletに追加されたコマンドレットで概ね代用できます。
ただし、一部の機能についてはコマンドレットでは代用できずWMIでWin32_Volumeクラスの機能を利用する必要があります。

PowerShellでmountvolコマンドを代用する

最初にWindows Server 2012 R2でmountvolコマンドのヘルプを見ると以下の様になっています。

PS C:\> mountvol /?
ボリューム マウント ポイントを作成、削除、一覧を表示します。

MOUNTVOL [ドライブ:]パス ボリューム名
MOUNTVOL [ドライブ:]パス /D
MOUNTVOL [ドライブ:]パス /L
MOUNTVOL [ドライブ:]パス /P
MOUNTVOL /R
MOUNTVOL /N
MOUNTVOL /E

    パス        マウント ポイントを常駐させる既存の NTFS ディレクトリ
                を指定します
    ボリューム名
                マウント ポイントのターゲットとなるボリューム名を指定しま
                す。
    /D          指定されたディレクトリからボリューム マウント ポイント
                を削除します。
    /L          指定されたディレクトリのマウントされているボリューム
                の一覧を表示します。
    /P          指定されたディレクトリからボリューム マウント ポイントを削除
                してボリュームをマウント解除し、ボリュームをマウントできな
                くします。
                ボリューム マウント ポイントを作成して、もう一度ボリュームを
                マウントできるようにします。
    /R          システムに存在しないマウント ポイント ディレクトリとレジストリ
                設定を削除します。
    /N          新しいボリュームの自動マウントを無効にします。
    /E          新しいボリュームの自動マウントを再び有効にします。

これからこの各オプションについてどう代用するか説明していきます。*1

ボリューム情報を表示する(MOUNTVOL /L)

ボリュームの表示はGet-Volumeコマンドが使えます。
mountvolコマンドと同等の情報を出力するには以下の様にすればOKです。

Get-Volume | Format-Table ObjectID, DriveLetter -AutoSize

実行例は以下。

PS C:\> Get-Volume | Format-Table ObjectID, DriveLetter -AutoSize

ObjectID                                          DriveLetter
--------                                          -----------
\\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\           
\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\           
\\?\Volume{a8cbd0fd-83fc-11e4-80b3-806e6f6e6963}\           C
\\?\Volume{99d153e8-2248-11e6-80d0-08002724e199}\           D    

ただし、ボリュームをドライブではなくディレクトリ上にマウントした場合はWin32_VolumeクラスのNameプロパティから情報をとる必要があります。
以下はボリューム\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\D:\OtherDrive\にマウントした場合の例になります。

PS C:\> Get-WmiObject -Class Win32_Volume | Format-Table DeviceID, Name

DeviceID                                                    Name
--------                                                    ----
\\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\           \\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\
\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\           D:\OtherDrive\
\\?\Volume{a8cbd0fd-83fc-11e4-80b3-806e6f6e6963}\           C:\
\\?\Volume{99d153e8-2248-11e6-80d0-08002724e199}\           D:\

ドライブレターを割り当てる(MOUNTVOL)

ドライブレターの割り当てについて、当初Set-Volumeが使えるのかなと思っていたのですが、こいつはボリュームのラベルを設定するためのコマンドレットでドライブレターの割り当てには使う事ができませんでした...
かなりわかりにくいのですが、ドライブレターの割り当てはボリュームからではなく、Add-PartitionAccessPathコマンドレットを使ってパーティションからアクセスパスを指定する形で行う必要があります。

上記の例でボリューム\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\にドライブレターEを割り当てる場合は以下の様にします。

$Drive = "E:\"
$ID = '\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\'
Get-Volume -ObjectId $ID | Get-Partition | Add-PartitionAccessPath -AccessPath $Drive

結果を確認すると以下の様になりドライブレターが割り当たっていることが確認できます。

PS C:\> Get-Volume | Format-Table ObjectID, DriveLetter -AutoSize

ObjectID                                          DriveLetter
--------                                          -----------
\\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\           
\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\           E
\\?\Volume{a8cbd0fd-83fc-11e4-80b3-806e6f6e6963}\           C
\\?\Volume{99d153e8-2248-11e6-80d0-08002724e199}\           D

ちなみに、Win32_Volumeクラスを使ってドライブレターを割り当てる場合はAddMountPointメソッドを使います。
Storage Cmdletが使えない環境では以下の様にすれば良いでしょう。

$Drive = "E:\"
$ID = '\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\'
$Volume = Get-WmiObject -Class Win32_Volume | Where-Object { $_.DeviceID -eq $ID }
$Volume.AddMountPoint($Drive)

ドライブレターの割り当てを解除する(MOUNTVOL /D)

ドライブレターの割り当てを解除するにはRmove-PartitionAccessPathコマンドレットを使います。
ドライブレターがわかっていればGet-Partitionから直ちにパーティションの情報はとれますので、以下の様にしてアクセスパスを削除してやればドライブレターも解除されます。

Get-Partition -DriveLetter "E" | Remove-PartitionAccessPath -AccessPath "E:\"

実行結果は以下となりドライブレターの割り当てがなくなっていることがわかります。

PS C:\> Get-Volume | Format-Table ObjectID, DriveLetter -AutoSize

ObjectID                                          DriveLetter
--------                                          -----------
\\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\           
\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\           
\\?\Volume{a8cbd0fd-83fc-11e4-80b3-806e6f6e6963}\           C
\\?\Volume{99d153e8-2248-11e6-80d0-08002724e199}\           D

また、Win32_Volumeクラスを使う場合はDriveLetterプロパティを$nullにすればOKです。
設定後Put()メソッドを使い変更を確定させる必要があります。

$Drive = "E:"
$Volume = Get-WmiObject -Class Win32_Volume | Where-Object { $_.DriveLetter -eq $Drive }
$Volume.DriveLetter = $null
$Volume.Put()

ボリュームをアンマウントする(MOUNTVOL /P)

ボリュームをアンマウントするのはStorage Cmdletから出来ず、Win32_VolumeクラスのDismount()メソッドを使うしかない様です。

Dismount()メソッドを使うには、あらかじめドライブレターを解除しておく必要があり、MOUNTVOL /Pと同等にするには第2引数を$trueにする必要があります。
これらをまとめると以下の様にすることでアンマウントできます。

$Drive = "E:"
$Volume = Get-WmiObject -Class Win32_Volume | Where-Object { $_.DriveLetter -eq $Drive }
$Volume.DriveLetter = $null
$Volume.Put()
$Volume.Dismount($false, $true)

尚、MOUNTVOL /DMOUNTVOL /Pの違いについては以下の記事を参考にしてください。

blogs.technet.microsoft.com

この記事の内容を踏まえて実行結果を確認すると以下の様になります。

PS C:\> Get-Volume | Format-Table ObjectID, DriveLetter -AutoSize

ObjectID                                          DriveLetter
--------                                          -----------
\\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\           
\\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\           
\\?\Volume{a8cbd0fd-83fc-11e4-80b3-806e6f6e6963}\           C
\\?\Volume{99d153e8-2248-11e6-80d0-08002724e199}\           D

PS C:\> mountvol

・・・(中略)・・・

現在のマウント ポイントとボリューム名の考えられる値:

    \\?\Volume{a8cbd0fc-83fc-11e4-80b3-806e6f6e6963}\
        *** マウント ポイントなし ***

    \\?\Volume{2ddbd13b-25b8-11e6-80d2-08002724e199}\
        *** ボリューム マウント ポイントが作成されるまでマウントできません ***

    \\?\Volume{a8cbd0fd-83fc-11e4-80b3-806e6f6e6963}\
        C:\

    \\?\Volume{99d153e8-2248-11e6-80d0-08002724e199}\
        D:\

新しいボリュームの自動マウントの設定を行う(MOUNTVOL /N)(MOUNTVOL /E)

新しいボリュームの自動マウント設定(MOUNTVOL /NおよびMOUNTVOL /EまたはDISKPART AUTOMOUNT)について、PowerShellで代替するにはレジストリの値を直接変更するしかない様です。

該当するレジストリキーは、Mountvol /N and diskpart automount disableKB822653によれば、

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MountMgr\NoAutoMountで、
0が"自動マウントする"、1が"自動マウントしない"との事です。

ただ、私が検証した範囲だとレジストリの変更とDISKPART AUTOMOUNTコマンドの結果が同期しませんでした...
原因は特定できなかったのですがこの程度であればPowerShellで代替せずmountvolコマンドをそのまま使った方が手っ取り早く確実でしょう。

システムに存在しないマウントポイント設定をクリアする(MOUNTVOL /R)

残念ながらMOUNTVOL /RをPowerShellで代替することはできない様で、この場合は素直にmountvolコマンドを使うしかないです。
正直なところあまり使用頻度の高いコマンドとも思えませんので代替できなくても問題ないと思います。

最後に

とりあえずこんな感じです。

ここまでの内容でわかるかと思いますが、mountvolコマンドとStorage Cmdlet、Win32_Volumeクラスの機能は綺麗に対比できる感じにはなっていません。

なので現実的にPowerShellで何かしようとする場合は両方を使い分ける必要があると思います。
基本はStorage Cmdletを使いつつ、どうしようもない場合はmountvolコマンドで対処するのが良いのではないでしょうか。

Storage cmdletで統一して操作できる様になればうれしいのですが、それはもうしばらく先の話になりそうです。

*1:MOUNTVOL /Sオプションについては情報を集めることができなかったので今回はスルーします...

PowerShellからコマンドプロンプトを呼んだ時にちょっとだけ便利にする方法

需要は少ないと思いますが、地味に便利な方法です。

はじめに

突然ですが質問です。
下の画像は何のプログラムを起動していると思いますか?

f:id:stknohg:20160527120253p:plain

正解はPowerShellコンソールから起動したコマンドプロンプト(cmd.exe)でした。

f:id:stknohg:20160527120320p:plain

Process Explorerで見るとこんな感じになっています。

f:id:stknohg:20160527120342p:plain

よく見るとプロンプト左のPSの部分が無いので違いがわかります。

PowerShellからコマンドプロンプトを呼んだ時にちょっと便利にする方法

普段PowerShellのコンソールを使っていて調査などで一時的にコマンドプロンプトを使うことがあると思います。

PowerShellからコマンドプロンプトを起動すると上図の様になり、パッと見で自分が作業してるのがPowerShellコンソールなのかコマンドプロンプトなのか見分けが付かなくなります。

この状態でたまーにコマンドプロンプトなのにPowerShellのコマンドを実行してしまうことがあったりして、まあ大して実害はないのですが地味に嫌な気分になりますw

そんな時は以下のコードをプロファイルに仕込むと少しだけ便利になります。

# PowerShellからコマンドプロンプトを呼んだ際のプロンプト文字列をカスタマイズ
$ENV:PROMPT = "PS(CMD) `$P`$G"

コマンドプロンプトのプロンプトに表示される文字列はPROMPT環境変数で設定することができます。
このため、PowerShellを起動したときにこの環境変数を変更してやるとPowerShellからコマンドプロンプトを呼び出した時だけプロンプトに表示する文字列を変えることができるわけです。

仕込みを入れるとPowerShellからコマンドプロンプトを起動した場合以下の様になり、

f:id:stknohg:20160527121712p:plain

プロンプトの文字列がPS(CMD) ...と変わっていることがわかります。
この表示であれば最初のよりは大分わかりやすいかと思います。

【おまけ】PowerShellをネストして実行した場合にちょっとだけ便利にする方法

本エントリの趣旨からは外れるのですが、私の場合PowerShellからPowerShellをネストして呼び出すこともたまにあるので以下のファンクションを使ってプロンプトにネストの段数を表示させたりもしています。
雑なコードなのでpowersehll.exe → cmd.exe → powershell.exeみたいな呼び出しをした場合は対応できませんが、それなりに使えています。

#
#   現在のPowerShell.exeのネストを取得します。
#   環境によっては Get-WmiObject を Get-CimInstance に変えると良い感じになると思います。
#
function Get-PowerShellNest()
{
    $Nest = 0
    $ParentPID = (Get-WmiObject -Class Win32_Process -Property ParentProcessId -Filter "ProcessId = $PID").ParentProcessId
    $Parent = Get-WmiObject -Class Win32_Process -Property Name, ProcessID, ParentProcessId -Filter "ProcessId = $ParentPID"
    while( $Parent.Name -eq "powershell.exe" )
    {
        $Nest += 1
        $ParentPID = $Parent.ParentProcessId
        $Parent = Get-WmiObject -Class Win32_Process -Property Name, ProcessID, ParentProcessId -Filter "ProcessId = $ParentPID"
    }
    return $Nest
}

#
#   Promptファンクションをオーバーライドします。
#
$Nest = $null
function prompt()
{
    if( $null -eq $Nest )
    {
        $Nest = Get-PowerShellNest
        $NestValue = ""
        if( $Nest -gt 0 )
        {
            $NestValue = "({0})" -F $Nest
        }
        # PowerShellからCmd.exeが呼ばれた際にプロンプト文字列をカスタマイズするための設定
        $ENV:PROMPT = "PS{0}(CMD) `$P`$G" -F $NestValue
    }
    # プロンプト文字列を変更
    return "PS{0} {1}> " -F $NestValue, (Get-Location)
}

実行結果はこんな感じになります。

f:id:stknohg:20160527122744p:plain

Windows 10でのNTPサーバーの指定方法

最近お仕事で使っているPCにもWindows 10化の波がきており、NTPサーバーの設定をしようとしたらUIがガラッと変わって面倒なことになっていたので備忘録を兼ねてメモっておきます。

GUIでの設定方法

Windows 10でタスクトレイの時刻から日付と時刻の設定を選択すると、

f:id:stknohg:20160525192009p:plain


【2017/07/13追記】
最新のWindows 10だとタスクバーを右クリックしないと日付と時刻の設定が出なくなってしまいました...

f:id:stknohg:20170713191606p:plain

f:id:stknohg:20170713191617p:plain

【追記ここまで】


以下の様に新しい日付と時刻の設定ダイアログが表示され、こいつにはNTPサーバーを指定するところがありません...

f:id:stknohg:20160525192131p:plain

ここからNTPサーバーを指定するには、このダイアログを下までスクロールさせて関連設定日付、時刻、地域の追加設定をクリックしてコントロールパネルを起動し、

f:id:stknohg:20160525192333p:plain

そこから日付と時刻の設定を選択、

f:id:stknohg:20160525192510p:plain

すると従来の日付と時刻の設定ダイアログが表示されるのでインターネット時刻タブからNTPサーバーの指定をすることができます。

f:id:stknohg:20160525192601p:plain

f:id:stknohg:20160525192634p:plain

率直に言ってかなり面倒なことになっています。

楽な設定方法

流石にこれは辛すぎるのでもう少し楽にできないか調べました。

従来の日付と時刻の設定ダイアログの実体はtimedate.cplファイルですのでコマンドプロンプトやPowerShellから

PS C:\> timedate.cpl

と打てば一発で日付と時刻の設定ダイアログを呼び出しNTPサーバーの設定をすることができます。

f:id:stknohg:20160525193003p:plain

これなら我慢できるレベルですね。

コマンドでの設定方法

さらに楽してすべてコマンドでやりたい場合はw32tmコマンドが使えます。

w32tmコマンドは昔からあるコマンドで使い方も変わっていません。
残念ながら今に至るまでこのコマンドを代替するPowerShell Cmdletは出ておらず、使えそうなPowerShellモジュールも無さげです。
なのでWindows 10でも頑張ってw32tmコマンドを使っていきます。

NTPサーバーを指定する場合はw32tm /configコマンドを以下の様な感じで実行すればOKです。
パラメーターの詳細はググればいくらでも出てきますので本エントリでは触れません(

w32tm /config /update /manualpeerlist:"time.windows.com,0x9" /syncfromflags:manual

時刻同期はw32tm /resyncコマンドで可能です。

w32tm /resync

設定値を確認する場合は、w32tm /query /statusw32tm /query /configurationコマンドが使えます。

w32tm /query /status
w32tm /query /configuration

ただし、w32tmコマンドからNTPサーバーの指定をするとインターネット時刻設定ダイアログの画面表示と実際の設定値が同期しないので注意が必要です。

ダイアログの画面表示はレジストリHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\DateTime\Serversの内容を基に設定されていますので、同期させたい場合はこのキーを適切に設定しする必要があります。
0から始まる連番のキーがダイアログのコンボボックスに表示されるNTPサーバーのリストになり、既定のキーにリストから選択したNTPサーバーの番号を設定します。

本エントリでは具体的な手順にまで触れませんが、頑張れば何とかできるレベルだと思いますので同期させたい場合は気合いでなんとかしてください。