Windows 8.1用にHDDを移行したとき、昔使っていた古いドライブレターをいくつか廃止して統合したので、参考資料画像群*1のしおりとして残しておいたショートカットファイル(*.lnk)のリンク切れが大量に発生しました(リンク先の実体ファイルは別ドライブの別ディレクトリに移動したため)。今日はこいつらをなんとかしてみます。
Windowsのショートカットファイルはバイナリ形式なので、リンク先などの中身を解析・編集するには、情報操作をするためのShell APIを使ったプログラムを書く必要があるのですが、今回はC#での一括処理を書いてみました*2。
ShortcutConverter.zip
参照設定に「Windows Script Host Object Model」アセンブリを追加しています。
といっても、ただショートカットファイルのターゲットパスをC#コードで置換するだけでは芸がないし応用も効かないので、
- まず古いショートカットファイルの内部情報を中間XMLとして書き出す(Lnk2Xml)
- パスの置換・正常化はテキストエディターで中間XMLに対して行なう
- 編集後の中間XMLから新たにショートカットファイルを生成する(Xml2Lnk)
という柔軟な多段処理を行なえるようにモジュールを分割します。
以下、処理の抜粋。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyShellHelpers
{
public static class ShellShortcutHelper
{
public static void XmlToLnk(string strInListXmlFilePath)
{
var shell = new IWshRuntimeLibrary.WshShell();
using (var stream = new System.IO.FileStream(strInListXmlFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
var xDoc = System.Xml.Linq.XDocument.Load(stream);
var rootNode = xDoc.Root;
var query =
from node in rootNode.Elements("Shortcut")
select node;
foreach (var elem in query)
{
var nonameData = new
{
ShortcutFilePath = elem.Attribute("ShortcutFilePath"),
TargetPath = elem.Attribute("TargetPath"),
Arguments = elem.Attribute("Arguments"),
WorkingDirectory = elem.Attribute("WorkingDirectory"),
Hotkey = elem.Attribute("Hotkey"),
WindowStyle = elem.Attribute("WindowStyle"),
Description = elem.Attribute("Description"),
IconLocation = elem.Attribute("IconLocation"),
};
System.Diagnostics.Debug.Assert(nonameData.ShortcutFilePath != null);
var objShortcut = shell.CreateShortcut(nonameData.ShortcutFilePath.Value) as IWshRuntimeLibrary.IWshShortcut;
System.Diagnostics.Debug.Assert(nonameData.TargetPath != null);
objShortcut.TargetPath = nonameData.TargetPath.Value;
System.Diagnostics.Debug.Assert(nonameData.Arguments != null);
objShortcut.Arguments = nonameData.Arguments.Value;
System.Diagnostics.Debug.Assert(nonameData.WorkingDirectory != null);
objShortcut.WorkingDirectory = nonameData.WorkingDirectory.Value;
System.Diagnostics.Debug.Assert(nonameData.Hotkey != null);
objShortcut.Hotkey = nonameData.Hotkey.Value;
System.Diagnostics.Debug.Assert(nonameData.WindowStyle != null);
objShortcut.WindowStyle = Int32.Parse(nonameData.WindowStyle.Value);
System.Diagnostics.Debug.Assert(nonameData.Description != null);
objShortcut.Description = nonameData.Description.Value;
System.Diagnostics.Debug.Assert(nonameData.IconLocation != null);
objShortcut.IconLocation = nonameData.IconLocation.Value;
objShortcut.Save();
}
}
}
public static void LnkToXml(IEnumerable<string> inShortcutFilePaths, string strOutListXmlFilePath)
{
var shell = new IWshRuntimeLibrary.WshShell();
var xmlSettings = new System.Xml.XmlWriterSettings();
xmlSettings.Indent = true;
xmlSettings.IndentChars = "\t";
xmlSettings.Encoding = new UTF8Encoding();
using (var stream = new System.IO.FileStream(strOutListXmlFilePath, System.IO.FileMode.Create, System.IO.FileAccess.Write))
{
using (var xmlWriter = System.Xml.XmlWriter.Create(stream, xmlSettings))
{
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("Shortcuts");
foreach (var path in inShortcutFilePaths)
{
System.Diagnostics.Debug.Assert(System.IO.File.Exists(path));
var objShortcut = shell.CreateShortcut(path) as IWshRuntimeLibrary.IWshShortcut;
xmlWriter.WriteStartElement("Shortcut");
xmlWriter.WriteStartAttribute("ShortcutFilePath");
xmlWriter.WriteString(path);
xmlWriter.WriteEndAttribute();
NOTE
xmlWriter.WriteStartAttribute("TargetPath");
xmlWriter.WriteString(objShortcut.TargetPath);
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartAttribute("Arguments");
xmlWriter.WriteString(objShortcut.Arguments);
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartAttribute("WorkingDirectory");
xmlWriter.WriteString(objShortcut.WorkingDirectory);
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartAttribute("Hotkey");
xmlWriter.WriteString(objShortcut.Hotkey);
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartAttribute("WindowStyle");
xmlWriter.WriteString(objShortcut.WindowStyle.ToString());
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartAttribute("Description");
xmlWriter.WriteString(objShortcut.Description);
xmlWriter.WriteEndAttribute();
xmlWriter.WriteStartAttribute("IconLocation");
xmlWriter.WriteString(objShortcut.IconLocation);
xmlWriter.WriteEndAttribute();
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
}
}
}
}
}
困ったことがあったらすぐに自動化ツールをさらっと書けるのがプログラマーの特権ですね。
XmlToLnk()は匿名型やLINQ to XMLを使うなど、やや技巧的に書いています。
なお、ショートカットファイルを読み込むときの注意点はLnkToXml()内のコメントに記載しています。Visual C#を使うときは、プロジェクト設定で[32 ビットの優先]というチェックボックスを外しておきましょう。