C# - .zi_ とか .ex_ とかの拡張子をDrag&Dropでリネームするツールをつくった

メールの添付ファイルとして拡張子`.zip`や`.exe`が禁止されている環境で、
ファイルをリネームして展開するのが地味に面倒なので、Drag&Dropでリネームするツールを作ってみた。
（※添付ファイルを開くときは慎重に！）

2020.01.03追記：完全にこれですね。。
[ITの7つの無意味な習慣](https://qiita.com/search?q=IT%E3%81%AE7%E3%81%A4%E3%81%AE%E7%84%A1%E6%84%8F%E5%91%B3%E3%81%AA%E7%BF%92%E6%85%A3)

2020.06.13追記：Drag&Dropすらめんどくさいので、拡張子に関連付けるのがおすすめ！！！

# 機能

Drag&Dropしたファイルの拡張子をリネームする。
拡張子の変換規則はプログラム内で決め打ちにしてある。

# ソースコード

```cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

// Note: "."が2つ以上ある場合は、最後の"."以降を拡張子とみなす
//  例： "hoge.tar.gz" の拡張子は "gz" とみなす
class FileRenamer : Form
{
    static Dictionary<string,string> _defaultExtensionDict = new Dictionary<string,string>(){
        // Key:変換前拡張子(小文字), Value:変換後拡張子
        // "."は付けない
        {"zi",  "zip"},
        {"zi_", "zip"},
        {"ex",  "exe"},
        {"ex_", "exe"},
        //{"",    "zip"},
    };

    FileRenamer()
    {
        Text = "File Renamer";
        ClientSize = new Size(200,200);

        AllowDrop = true;
        DragEnter += (sender,e)=>{
            if (e.Data.GetDataPresent(DataFormats.FileDrop)){
                e.Effect = DragDropEffects.Copy;
            }
            else{
                e.Effect = DragDropEffects.None;
            }
        };
        DragDrop += (sender,e)=>{
            var fileNames = (string[]) e.Data.GetData(DataFormats.FileDrop, false);
            foreach(string s in fileNames) {
                RenameFile(_defaultExtensionDict, s);
            }
        };
    }

    static bool RenameFile(Dictionary<string,string> extentionConvertionDict, string partialFileName)
    {
        var fi = new FileInfo(partialFileName);
        if ( !fi.Exists ) {
            return false;
        }
        string newName = GetRenamedFullName(extentionConvertionDict, fi);
        if ( newName == null ) {
            return false;
        }

        Console.WriteLine(newName);

        try {
            File.Move(fi.FullName, newName);
        }
        catch (UnauthorizedAccessException e) {
            Console.WriteLine(e);
        }
        catch (PathTooLongException e) {
            Console.WriteLine(e);
        }
        catch (DirectoryNotFoundException e) {
            Console.WriteLine(e);
        }
        catch (IOException e) {
            Console.WriteLine(e);
        }

        return true;
    }

    // return null : 変更対象ではない or エラー
    static string GetRenamedFullName(Dictionary<string,string> extentionConvertionDict, FileInfo fi)
    {
        string oldExtension;

        int posDot = fi.Name.LastIndexOf(".");
        if ( posDot < 0 ) {       // 拡張子がない
            oldExtension = "";
        }
        else if ( posDot == 0 ) { // "."から始まっている(ファイル名部分がない)
            return null;
        }
        else if ( posDot == fi.Name.Length-1 ) { // "."で終わっている
            return null;
        }
        else { // posDot >= 1 (拡張子がある)
            oldExtension = fi.Name.Substring(posDot+1); // "."を含まない拡張子部分
        }

        oldExtension = oldExtension.ToLowerInvariant(); // 小文字に変換する

        string newExtension;
        if (extentionConvertionDict.TryGetValue(oldExtension, out newExtension)){
            return Path.ChangeExtension(fi.FullName, newExtension);
        }
        else {
            return null; // 置換対象ではない
        }
    }
    
    [STAThread]
    static void Main(string[] args)
    {
        if ( args.Length >= 1 ) {
            foreach ( string s in args ) {
                RenameFile(_defaultExtensionDict, s);
            }
        }
        else {
            Application.Run(new FileRenamer());
        }
    }
}
```


# 続編 - Outlookに添付されている.zi_や.ex_ファイルを楽に開く

`.zi_`ファイル や `.ex_`ファイル に関連付けして使用する想定。

Outlookの添付フォルダと思われるフォルダ（下記ソースコード中の`_outlookAttachmentPath`で指定）内のファイルだった場合に、マイドキュメントに日時に依存する名前のフォルダを生成してファイルをコピーします。その後に`zip`や`exe`へのリネームをトライし、リネームできた場合は、そのまま開きます。

※作業ステップを極限まで減らしたことで、スパムを踏むリスクが上がる恐れがあります。（セキュリティ上はちょっと危険です。）

```csharp

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

// Note: "."が2つ以上ある場合は、最後の"."以降を拡張子とみなす
//  例： "hoge.tar.gz" の拡張子は "gz" とみなす
class FileRenamer : Form
{
    // Outlook添付ファイル C:\Users\UserName\AppData\Local
    static readonly string _outlookAttachmentPath  = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Windows\INetCache\Content.Outlook\");  // "
    static readonly string _temporaryDestPath  = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

    static readonly int _maxTemporaryDirN = 99;

    static Dictionary<string,string> _defaultExtensionDict = new Dictionary<string,string>(){
        // Key:変換前拡張子(小文字), Value:変換後拡張子
        // "."は付けない
        {"zi",  "zip"},
        {"zi_", "zip"},
        {"ex",  "exe"},
        {"ex_", "exe"},
        //{"",    "zip"},
    };

    FileRenamer()
    {
        Text = "File Renamer";
        ClientSize = new Size(200,200);

        AllowDrop = true;
        DragEnter += (sender,e)=>{
            if (e.Data.GetDataPresent(DataFormats.FileDrop)){
                e.Effect = DragDropEffects.Copy;
            }
            else{
                e.Effect = DragDropEffects.None;
            }
        };
        DragDrop += (sender,e)=>{
            var fileNames = (string[]) e.Data.GetData(DataFormats.FileDrop, false);
            foreach(string s in fileNames) {
                RenameFile(_defaultExtensionDict, s);
            }
        };
    }

    static string RenameFile(Dictionary<string,string> extentionConvertionDict, string partialFileName)
    {
        var fi = new FileInfo(partialFileName);
        if ( !fi.Exists ) {
            return null;
        }
        string newName = GetRenamedFullName(extentionConvertionDict, fi);
        if ( newName == null ) {
            return null;
        }

        Console.WriteLine(newName);

        try {
            File.Move(fi.FullName, newName);
        }
        catch (UnauthorizedAccessException e) {
            Console.WriteLine(e);
        }
        catch (PathTooLongException e) {
            Console.WriteLine(e);
        }
        catch (DirectoryNotFoundException e) {
            Console.WriteLine(e);
        }
        catch (IOException e) {
            Console.WriteLine(e);
        }

        return newName;
    }

    // return null : 変更対象ではない or エラー
    static string GetRenamedFullName(Dictionary<string,string> extentionConvertionDict, FileInfo fi)
    {
        string oldExtension;

        int posDot = fi.Name.LastIndexOf(".");
        if ( posDot < 0 ) {       // 拡張子がない
            oldExtension = "";
        }
        else if ( posDot == 0 ) { // "."から始まっている(ファイル名部分がない)
            return null;
        }
        else if ( posDot == fi.Name.Length-1 ) { // "."で終わっている
            return null;
        }
        else { // posDot >= 1 (拡張子がある)
            oldExtension = fi.Name.Substring(posDot+1); // "."を含まない拡張子部分
        }

        oldExtension = oldExtension.ToLowerInvariant(); // 小文字に変換する

        string newExtension;
        if (extentionConvertionDict.TryGetValue(oldExtension, out newExtension)){
            return Path.ChangeExtension(fi.FullName, newExtension);
        }
        else {
            return null; // 置換対象ではない
        }
    }
    
    static string HandleOutlookAttachment(Dictionary<string,string> extentionConvertionDict, string path)
    {
        if ( path.ToLower().StartsWith(_outlookAttachmentPath.ToLower()) ) {
            DateTime dt = DateTime.Now;
            string dtStr = dt.ToString("yyMMdd_HHmm");

            string tmpDirBase = Path.Combine( _temporaryDestPath , "RenameZi_"+dtStr);
            
            string tmpDir = null;
            bool foundFlg = false;
            
            for (int i=0;i<_maxTemporaryDirN;i++) {
                tmpDir = tmpDirBase+"_"+i.ToString().PadLeft(2,'0');
                if ( ! Directory.Exists(tmpDir) && ! File.Exists(tmpDir) ) {
                    foundFlg = true;// 未使用フォルダを発見できた
                    break;
                }
            }

            if ( foundFlg ) {
                Directory.CreateDirectory(tmpDir);
                string newPath = Path.Combine(tmpDir, Path.GetFileName(path));
                File.Copy(path, newPath);
                return newPath;
            }
            else {
                // failed to create temporary folder
                return path;
            }
        }
        else {
            return path;
        }
    }

    [STAThread]
    static void Main(string[] args)
    {
        if ( args.Length >= 1 ) {
            foreach ( string s in args ) {
                string path = HandleOutlookAttachment(_defaultExtensionDict, s);
                string renamedPath = RenameFile(_defaultExtensionDict, path);
                if ( renamedPath != null ) {
                    // open with default program  ※セキュリティ上はキケンです
                    System.Diagnostics.Process.Start(renamedPath);
                }
            }
        }
        else {
            Application.Run(new FileRenamer());
        }
    }
}

```