スキップしてメイン コンテンツへ

 none
Windows Script HostのWshShortcutオブジェクトのプロパティが不適切な値を返すことがあります。 RRS feed

  • 質問

  • 特にTargetPathプロパティに環境変数入りのパスが入っているとき、環境変数が変わってもプロパティの値が変わらないことがよくあります。

    再現方法を説明します。

    まず、 "C:\test" フォルダーを作成し、この中に以下のファイル・フォルダーを用意します。

    ショートカットのターゲットとなるファイル :

    • a.txt
    • b.txt

    ショートカットのアイコン :

    • a.ico
    • b.ico

    ショートカットの作業フォルダーとなる空フォルダー :

    • a\
    • b\

    次に、ターゲット、作業フォルダーおよびアイコンに環境変数入りパスを指定したショートカット "test.lnk" を作るための以下のVBScriptファイル "createlnk.vbs" を用意します。

    Option Explicit
    
    Dim Fso
    Dim WshShell
    Dim ThisFolderPath
    Dim LnkPath
    Dim WshShortcut
    
    Set Fso = CreateObject( "Scripting.FileSystemObject" )
    Set WshShell = CreateObject( "WScript.Shell" )
    ThisFolderPath = Fso.GetParentFolderName( WScript.ScriptFullName )
    LnkPath = Fso.BuildPath( ThisFolderPath, "test.lnk" )
    
    Set WshShortcut	= WshShell.CreateShortcut( LnkPath )
    WshShortcut.TargetPath = Fso.BuildPath( ThisFolderPath, "%v%.txt" )
    WshShortcut.WorkingDirectory = Fso.BuildPath( ThisFolderPath, "%v%" )
    WshShortcut.IconLocation = Fso.BuildPath( ThisFolderPath, "%v%.ico" ) & ",0"
    WshShortcut.Save
    
    

    そして、作成したショートカット "test.lnk" のターゲット、作業フォルダーおよびアイコンを表示する以下のVBScriptファイル "dumplnk.vbs" を用意します。

    Option Explicit
    
    Dim Fso
    Dim StdOut
    Dim WshShell
    Dim ThisFolderPath
    Dim LnkPath
    Dim WshShortcut
    
    Set Fso = CreateObject( "Scripting.FileSystemObject" )
    Set StdOut = WScript.StdOut
    Set WshShell = CreateObject( "WScript.Shell" )
    ThisFolderPath = Fso.GetParentFolderName( WScript.ScriptFullName )
    LnkPath = Fso.BuildPath( ThisFolderPath, "test.lnk" )
    
    Set WshShortcut	= WshShell.CreateShortcut( LnkPath )
    StdOut.WriteLine "ターゲット : " & WshShortcut.TargetPath
    StdOut.WriteLine "作業フォルダー : " & WshShortcut.WorkingDirectory
    StdOut.WriteLine "アイコン : " & WshShortcut.IconLocation
    
    

    これで準備は完了です。

    それでは再現実験に入ります。

    まず、コマンドプロンプトで "C:\test" フォルダーを開きます。

    >cd /d C:\test
    

    環境変数 %v% に "a" という文字列を設定します。

    >set v=a
    

    ショートカット "test.lnk" の作成を実行します。

    >cscript createlnk.vbs
    

    ショートカット "test.lnk" の各プロパティを表示してみます。

    >cscript dumplnk.vbs
    

    出力は以下の通りです。

    ターゲット : C:\test\a.txt
    作業フォルダー : C:\test\%v%
    アイコン : C:\test\a.ico,0
    

    一見正しい値のように見えます。

    ショートカットを実行してみます。

    >test.lnk
    

    このとき確かに "a.txt" ファイルが関連付けされたアプリで開かれました。

    ここで、ショートカットの更新日時を確認すると、 2016/07/19  19:00 となっており作成日時と同一でした。また、ショートカット自体をメモ帳で開くと、

    L        タ      FロB      FH酖乍ムFH酖乍ムFH酖乍ム                      ナ  P
    澎ミ ・i「リ +00・ /C:\                   J 1     「N test  6   ・n
    ?「N*   'I    Q               t e s t    L 2    1@  a.txt 8   ・1
    @1@*   セ    |               a . t x t      G            8       F         
    ・   W******** C:\test\a.txt   . \ a . t x t  C : \ t e s t \ % v %  
    C : \ t e s t \ % v % . i c o (   	     1SPS竓XFシL8Cサ・・藁
    ホ            C:\test\%v%.txt
                                                          
                                                                                                    
                                                       
                                               
     C : \ t e s t \ % v % . t x t   
                                                                                                                        
                                                                                                                        
                           
                           
                            
                            
                                                                                                                         
                                                     %SystemDrive%\test\%v%.ico
                                                                                                    
                                                                                                    
                                              
    % S y s t e m D r i v e % \ t e s t \ % v % . i c o  
                                                                                                            
               
                                                                                                            
                                                                                                            
                                                                                                                 
                                       `    
     X       ********  ェf]ケ・ルEキ"uis.:襠ナfM・㊥D7貉DNェf]ケ・ルE
    キ"uis.:襠ナfM・㊥D7貉DN    
    

    となっており、作業フォルダーとアイコンのパスに関しては環境変数入りのもののみだがターゲットのパスは環境変数入りのものと作成時点でこれを展開したものとの両方が記述されているのが分かります。

    では次に、環境変数 %v% を "b" に変えてみます。

    >set v=b
    

    この状態でショートカットの各プロパティを表示すると、

    >cscript dumplnk.vbs
    
    ターゲット : C:\test\a.txt
    作業フォルダー : C:\test\%v%
    アイコン : C:\test\b.ico,0
    

    となりました。作業フォルダー(WorkingDirectoryプロパティ)は環境変数入りのパスで正しく、アイコン(IconLocationプロパティ)は現在の値で環境変数を展開したもので一応正しいのだが、ターゲット(TargetPathプロパティ)は環境変数を変える前のときのままになってしまっています。

    しかし、このショートカットを、

    >test.lnk
    

    とやって実行すると、ちゃんと "b.txt" が開かれ、環境変数入りのターゲットパスがWindowsによって正しく解釈されたことが分かります。

    また、Windowsの仕様でショートカットは実行するとその中身が最新情報に更新されます。上記の実行作業後、ショートカットの更新日時は 2016/07/19  19:58 に変わっていました。各プロパティは、

    >cscript dumplnk.vbs
    
    ターゲット : C:\test\b.txt
    作業フォルダー : C:\test\%v%
    アイコン : C:\test\b.ico,0
    

    となっており、ターゲットが正しいものになっていました。メモ帳でショートカットを開くと、

    L        タ      FロB      bチ纉瞋bチ纉瞋bチ纉瞋                      ナ  P澎ミ ・i「
    リ +00・ /C:\                   J 1     S test  6   ・n?S*   '
    I    Q               t e s t    L 2    M@  b.txt 8   ・M@M@
    *       ・               b . t x t      G            8       F         
    ・   ******** C:\test\b.txt
       . \ b . t x t 
     C : \ t e s t \ % v %
      C : \ t e s t \ % v % . i c o
     (   	     1SPS竓XFシL8Cサ・・藁ホ            
    C:\test\%v%.txt
                                                                                                                                                                                             
                                                               
     C : \ t e s t \ % v % . t x t
                                                                                                   
                                                                                                    
                                                                                                    
                                                                                                   
                                                                                                                     
    %SystemDrive%\test\%v%.ico
         
                                                                                                    
                                                                                                     
                                        
    % S y s t e m D r i v e % \ t e s t \ % v % . i c o 
                                                                                                    
                                                                                                     
                                                                                                    
                                                                                                       
                                                                                    `     X       ********   ェf]ケ・ルEキ"uis.灘鉀fM・㊥D7貉DNェf]ケ・ルEキ"ui
    s.灘鉀fM・㊥D7貉DN    

    となっており、ターゲットに関しては実行による更新時点での環境変数を展開した値と展開前の環境変数入りのパスとの両方が記述されています。

    このことから考えられることは、ターゲットパスに環境変数が入っているショートカットファイルは、展開前の環境変数入りのパスと作成または更新の時点で環境変数を展開した値との両方を保持しており、実行時には前者が参照されるがWshShortcutオブジェクトのTargetPathプロパティは後者のほうを返してしまっているということです。また、作業フォルダーおよびアイコンに関してはショートカットファイル内では展開前の環境変数入りのパスで保持されているが、IconLocationプロパティは環境変数を展開して値を返してしまっているということです。

    このため、WSHを使ってショートカットの管理を自動化できず大変困っています。諸般の事情でターゲットなどに環境変数を使ったショートカットをスタートメニューやデスクトップなどに置くことが多いので、正しいプロパティを、できれば展開前の環境変数入りの値を扱えるようにしなければなりません。

    WSHの代わりにPowerShellが使えないかと思ったのですが、調べてみると、PowerShellでショートカットを扱うためには間接的にWshShortcutオブジェクトを呼び出さなければならないみたいです。また、.NET(VBまたはC#)を使う場合でもやはり間接的にWshShortcutオブジェクトを呼び出す必要があるみたいです。WshShortcutオブジェクトにこの不具合(仕様?)がある限りどうにもなりません。このままだとC/C++でショートカットを操作するWin32 APIを呼び出す本格的なプログラミングに手を出さなければいけない可能性があります。

    もし正しくショートカットのプロパティを扱える代替のCOMコンポーネントがあればそれを使いたいのですが、どなたかご存知ならばぜひとも教えてもらいたいです。COMコンポーネントが存在しなければショートカットのプロパティを標準出力に書き出すだけのコンソールアプリでもかまわないです。WshShell.Execメソッドを使えば何とかなりそうだからです。

    現在の環境は、

    • OS : Windows 7 Pro SP1 (32bit)
    • WSH : 5.8

    です。

    2016年7月19日 11:56

回答

  • 生成された test.lnk のプロパティを開くと、ターゲットは"C:\test\%v%.txt" と書かれており、それをダブルクリックすると、同じ場所にあった "C:\test\%v%.txt" が開かれました。(ダブルクリックで起動した時に、.lnk の更新日時も書き換わりました)

    また、%v%.txt が無い場合は a.txt が開かれました。
    a.txt も b.txt も無い場合は c.txt が開かれていたので、環境変数が割り当てられていなかった場合でも、ショートカットの追跡機能によって処理されるようです。

    ショートカットには、失ったリンクを回復する機構が備わっており、移動またはリネームされたとしても、ある程度追跡してくれます。追跡の詳しい仕様までは把握していませんが…。
    Windows7のレジストリでショートカットのリンク切れ追跡等を停止する方法を教えてください

    > もし正しくショートカットのプロパティを扱える代替のCOMコンポーネントがあればそれを使いたいのですが、

    ショートカットの操作は IShellLink インターフェイスを通じて行う事になります。
    この点は Win95/NT4.0 当時から変わっていません。

    最初に cscript createlnk.vbs を呼び出した直後のショートカットに対し、
    IShellLink の GetPath メソッド を
    SLGP_RAWPATH 指定で呼び出せば "C:\test\%v%.txt" が返されますし、
    SLGP_SHORTPATH 指定で呼び出せば、"C:\test\a.txt" が返されます。

    なお、WshShortcut オブジェクトShellLinkObject オブジェクト は IShellLink インターフェイスのラッパーオブジェクトです。多少毛色が異なりますが URL ショートカット用の WshUrlShortcut オブジェクト などもそうですね。WMI だとWin32_ShortcutFile クラス とか。

    2016年7月20日 1:10
  • 検索してみましたが、今の所、SLGP_RAWPATH 指定な実装を持つ、WSH 対応の COM オブジェクトは見つかっていません。VBScript はおろか、JScript.NET や PowerShell でも単独では厳しそうです。

    代案として、ADODB.Stream あたりを用いて、.lnk ファイルのバイナリを解析して取得する方向で検討してみたところ、LINKINFO や RELATIVEPATH であれば、文字列表現として取り出せそうです。オフセット 0x004C 以降にある LINKTARGET_IDLIST については、シェル名前空間を示す ITEMIDLIST 型のバイナリー表現がブラックボックスなので手が出せませんでしたが。(VBS だと SHGetPathFromIDList を使うわけにも行きませんし)

    ただ、バイナリ取得には確実性が無いので、結局の所、IShellLink を使うのが最も手っ取り早い気がします。

    (スクリプトから呼べるように、こちらでもショートカット操作用の ActiveX EXE なコンソールアプリを試作してみようかな…)

    • 回答としてマーク fzok4234 2016年7月22日 11:22
    2016年7月21日 4:23
  • ショートカットのプロパティを標準出力に書き出すだけのコンソールアプリでもかまわないです。

    手抜き実装ですが、とりあえず作ってみました。exe だけ OneDrive に上げておきます。

    ShellLink201607211614.zip

    『ShellLinkLib test.lnk GetPath』
    →「C:\test\a.txt」
    
    『ShellLinkLib test.lnk GetPath 4』
    →「C:\test\%v%.txt」
    
    『ShellLinkLib test.lnk GetPath SLGP_RAWPATH』
    →「C:\test\%v%.txt」
    
    『ShellLinkLib test.lnk GetWorkingDirectory』
    →「C:\test\%v%」

    ソースコード(C#)は、少し手を加えてから GitHub に置きます。

    2016年7月21日 7:36

すべての返信

  • 生成された test.lnk のプロパティを開くと、ターゲットは"C:\test\%v%.txt" と書かれており、それをダブルクリックすると、同じ場所にあった "C:\test\%v%.txt" が開かれました。(ダブルクリックで起動した時に、.lnk の更新日時も書き換わりました)

    また、%v%.txt が無い場合は a.txt が開かれました。
    a.txt も b.txt も無い場合は c.txt が開かれていたので、環境変数が割り当てられていなかった場合でも、ショートカットの追跡機能によって処理されるようです。

    ショートカットには、失ったリンクを回復する機構が備わっており、移動またはリネームされたとしても、ある程度追跡してくれます。追跡の詳しい仕様までは把握していませんが…。
    Windows7のレジストリでショートカットのリンク切れ追跡等を停止する方法を教えてください

    > もし正しくショートカットのプロパティを扱える代替のCOMコンポーネントがあればそれを使いたいのですが、

    ショートカットの操作は IShellLink インターフェイスを通じて行う事になります。
    この点は Win95/NT4.0 当時から変わっていません。

    最初に cscript createlnk.vbs を呼び出した直後のショートカットに対し、
    IShellLink の GetPath メソッド を
    SLGP_RAWPATH 指定で呼び出せば "C:\test\%v%.txt" が返されますし、
    SLGP_SHORTPATH 指定で呼び出せば、"C:\test\a.txt" が返されます。

    なお、WshShortcut オブジェクトShellLinkObject オブジェクト は IShellLink インターフェイスのラッパーオブジェクトです。多少毛色が異なりますが URL ショートカット用の WshUrlShortcut オブジェクト などもそうですね。WMI だとWin32_ShortcutFile クラス とか。

    2016年7月20日 1:10
  • WshShortcutの代わりにShellLinkObjectを使用して、Pathを取得する前にResolveメソッドを使用するのはどうでしょうか。

    ShellLinkObjectは、CreateObject("Shell.Application")からいくつかの手順を踏んで取得します(詳細は調べてください)。ショートカットを新規作成する場合、空の.lnkファイルを作成しておいてそのFolderItemからShellLinkObjectを取ればいいです。

    2016年7月20日 6:25
  • > 生成された test.lnk のプロパティを開くと、ターゲットは"C:\test\%v%.txt" と書かれており、それをダブルクリックすると、同じ場所にあった "C:\test\%v%.txt" が開かれました。(ダブルクリックで起動した時に、.lnk の更新日時も書き換わりました)

    > また、%v%.txt が無い場合は a.txt が開かれました。
    a.txt も b.txt も無い場合は c.txt が開かれていたので、環境変数が割り当てられていなかった場合でも、ショートカットの追跡機能によって処理されるようです。

    確かに、環境変数 %v% が設定されていない親プロセスから "createlnk.vbs" でのショートカットの作成と実行の両方を行うと "C:\test\%v%.txt" が開かれます。例えば、コントロール パネル->ユーザー アカウントと家族のための安全設定->ユーザー アカウント->環境変数の変更から環境変数 %v% を定義していない状態でエクスプローラーから "createlnk.vbs" やできたショートカットのアイコンをダブルクリックした場合です。

    質問では環境変数 %v% が設定された親プロセスからショートカットの作成と実行を行うことを前提としています。ここでは、

    >set v=a

    として環境変数が設定されたコマンドプロンプトから全ての操作を行っていますが、実際の運用ではコントロールパネルからシステム環境変数を設定したり、 %APPDATA% などの組み込み環境変数を利用したりしています。

    なお、エクスプローラーでショートカットのプロパティダイアログを表示した場合は、作成時の親プロセスで環境変数が設定されていたかどうかを問わず、ターゲットや作業フォルダーのパスは環境変数入りとなります。

    それから、Hongliangさんへの返事に詳しいのですが、ShellLinkObjectを使ってみたのですが、ターゲットが常に展開されてしまうのでこれはWshShortcutの代替としては不向きです。

    IShellLinkインターフェイス自体はWSHからは使えず、不慣れなPowerShellか.Net(Visual Studio Community 2015)で今後テストすることになります。



    • 編集済み fzok4234 2016年7月20日 21:29 この返事ではBSTRについての説明は省く
    2016年7月20日 15:09
  • 試しに、以下のShellLinkObjectを使用したVBScriptファイル "dumplnk2.vbs" を作ってみました。

    Option Explicit
    
    Dim Fso
    Dim StdOut
    Dim ThisFolderPath
    Dim Shell
    Dim ThisFolder
    Dim ThisFolderItems
    Dim LnkFile
    Dim Lnk
    
    Set Fso = CreateObject( "Scripting.FileSystemObject" )
    Set StdOut = WScript.StdOut
    ThisFolderPath = Fso.GetParentFolderName( WScript.ScriptFullName )
    Set Shell = CreateObject( "Shell.Application" )
    Set ThisFolder = Shell.NameSpace( ThisFolderPath )
    Set ThisFolderItems = ThisFolder.Items()
    Set LnkFile = ThisFolderItems.Item( "test.lnk" )
    Set Lnk = LnkFile.GetLink
    
    Lnk.Resolve 0
    
    StdOut.WriteLine "ターゲット : " & Lnk.Path
    StdOut.WriteLine "作業フォルダー : " & Lnk.WorkingDirectory
    

    実行結果は、ターゲットは現在の環境変数が展開されたもので(環境変数を設定してない場合は未展開の環境変数入りとなる)、作業フォルダーは未展開の環境変数入りのパスでした。

    >set v=a
    
    >cscript createlnk.vbs
    
    >cscript dumplnk2.vbs
    
    ターゲット : C:\test\a.txt
    作業フォルダー : C:\test\%v%
    
    >set v=b
    
    >cscript dumplnk2.vbs
    
    ターゲット : C:\test\b.txt
    作業フォルダー : C:\test\%v%
    
    "dumplnk2.vbs" の実行前後で更新日時は変化しませんでした。

    また、 "Lnk.Resolve 0" の行をコメントアウトした場合はWshShortcutを使用したときとと全く同じ挙動でした。

    問題なのは、これでは未展開の環境変数入りのターゲットが取得できないことと、BSTR型の変数を扱えないVBScriptではアイコンパスが取得できないことです。アイコンパスも含め未展開の環境変数入りのパスを編集するような用途には不向きです。


    • 編集済み fzok4234 2016年7月20日 21:33 BSTRについて触れる
    2016年7月20日 15:49
  • 検索してみましたが、今の所、SLGP_RAWPATH 指定な実装を持つ、WSH 対応の COM オブジェクトは見つかっていません。VBScript はおろか、JScript.NET や PowerShell でも単独では厳しそうです。

    代案として、ADODB.Stream あたりを用いて、.lnk ファイルのバイナリを解析して取得する方向で検討してみたところ、LINKINFO や RELATIVEPATH であれば、文字列表現として取り出せそうです。オフセット 0x004C 以降にある LINKTARGET_IDLIST については、シェル名前空間を示す ITEMIDLIST 型のバイナリー表現がブラックボックスなので手が出せませんでしたが。(VBS だと SHGetPathFromIDList を使うわけにも行きませんし)

    ただ、バイナリ取得には確実性が無いので、結局の所、IShellLink を使うのが最も手っ取り早い気がします。

    (スクリプトから呼べるように、こちらでもショートカット操作用の ActiveX EXE なコンソールアプリを試作してみようかな…)

    • 回答としてマーク fzok4234 2016年7月22日 11:22
    2016年7月21日 4:23
  • ショートカットのプロパティを標準出力に書き出すだけのコンソールアプリでもかまわないです。

    手抜き実装ですが、とりあえず作ってみました。exe だけ OneDrive に上げておきます。

    ShellLink201607211614.zip

    『ShellLinkLib test.lnk GetPath』
    →「C:\test\a.txt」
    
    『ShellLinkLib test.lnk GetPath 4』
    →「C:\test\%v%.txt」
    
    『ShellLinkLib test.lnk GetPath SLGP_RAWPATH』
    →「C:\test\%v%.txt」
    
    『ShellLinkLib test.lnk GetWorkingDirectory』
    →「C:\test\%v%」

    ソースコード(C#)は、少し手を加えてから GitHub に置きます。

    2016年7月21日 7:36
  • 魔界の仮面弁士さん回答ありがとうございます。

    さすがにショートカットのバイナリ解析という荒業は今後OSのアップデートによる仕様変更のリスクが付きまとうため手を出しにくいです。当面はIShellLinkインターフェイスを用いた.NETアプリ製作でしのぐことになりますが、こちらでも有用なコンソールアプリがないかもうしばらく探してみます。

    2016年7月21日 7:50
  • 魔界の仮面弁士さんありがとうございます !!  わざわざアプリを作ってくれるなんてもうなんてお礼をしたらよいのか ...  これでしばらくはWSHのWshScriptExecオブジェクトのStdInで読み取って活用させていただくことになります。重ね重ねありがとうございました。

    それから、IShellLinkインターフェイスを用いたVB.NETのコードの例が

    http://stackoverflow.com/questions/18885865/using-ishelllink-interface-to-resolve-a-shortcut-which-has-changed-their-drive-l

    にありましたが、結構煩雑なコーディングを必要としています。.NET(特にVB)に慣れたらVBScriptとは比べ物にならない複雑な自動処理ができるから早く身に付けたいです。今後、WshShortcut風の使い勝手のClassを.NETで作りこんでおいてこれを使ってショートカットの管理を自動化していきたいと思っています。

    2016年7月21日 8:30