2018年5月20日日曜日

Windowsファイル履歴のエラー

Windows ファイル履歴実行されない場合の対処法
(同一フォルダ内の全角/半角同名のファイルをチェックするJSスクリプト)

しばらく前からWindows 10のファイル履歴が更新されなくなっていました。
イベントログを見るとエラーが発生しています。

コントロールパネル > ファイル履歴 > 詳細設定 > ファイル履歴イベントログにつぎのエラーが書かれていました。

C:\Users\(userName)\AppData\Local\Microsoft\Windows\FileHistory\Configuration\Config でユーザー ライブラリの変更のスキャンと変更されたファイルのバックアップを実行できません

これは、同じフォルダーに、全角と半角で同じ名前のファイルが保存されていると、正しくバックアップできないために発生するとのこと。

マイクロソフト コミュニテー
ファイル履歴が動作しない 

富士通Q&A
[Windows 10] ファイル履歴で正しくバックアップされているかどうかを確認する方法を教えてください。

私の場合にも
確かに上記のファイル名に該当するものがありましたが、それを直してもまだエラーが発生しました。そこで次の方法を試したところ回復しました。ライブラリに含まれるドキュメントなどの場所を変更したりしていたので、何かConfigがおかしくなったのかもしれません。
  • 次のフォルダを削除(リネームし成功したら削除する方が安全)
    C:\Users\(userName)\AppData\Local\Microsoft\Windows\FileHistory
  • コントロールパネル > ファイル履歴でドライブ選択し「今すぐ実行」
問題となるファイルを探すのに目視で調べるのは厄介なので、javascriptでファイル名のダブりをチェックするスクリプトを作ってみました。このスクリプトで”要チェック”と表示されるファイル/フォルダ名を調べてみてください。エラーにならない場合もどちらか一方しかファイル履歴に保存されていない可能性がありますので、チェックしてみる価値はあるでしょう。

このブログの後半にソースを載せてあります。

このスクリプトには全角/半角文字の対象リストがあります。私の場合はこれで問題になっていたファイルが見つけられましたが、このリストに問題になる文字が全て含まれているかわかりません。また、このスクリプトで”要チェック”とされても、実際には問題ない場合もあるでしょう。

状況はいささか複雑で、ファイル履歴が更新されるがいずれか一方しか履歴に保存されない場合もあり、またファイル履歴に保存・更新される順序でエラーになったりならなかったりすることもあるようです(手間がかかるので十分調べていません)。
また、フォルダ名とファイル名の間でも同様のことが発生します。

以下は次の環境で試した結果です。Windowsのバージョンによって動作が異なるかもしれません。

テスト環境:Window 10 Pro, バージョン 1709, OS ビルド 26299.431
  • エラーが発生する例:
    space .txt <=> space .txt  (全角/半角スペース)
    space ].txt <=> space ].txt  (全角/半角スペース)
    TEST1.txt <=> TEST1.txt (全角/半角数字)
    test  <=> TEST (半角フォルダ名と全角ファイル名)  
  • エラーは発生しないがどちらか一方しか履歴に残らない例:
    space[ ].txt <=> space[ ].txt  (全角/半角スペース)
    TEST.TXT <=> test.txt  (全角/半角アルファベット、ピリオドいずれも半角)
    test#  <=> TEST# (半角フォルダ名と全角ファイル名)
なぜこんなことが起こるか考えてみました。

ポイントは、エラーは発生しないがどちらか一方しか履歴に残らない場合がある、ということでしょう。エクスプローラにとっては別ファイルだが、ファイル履歴にとっては同一ファイルとみなされる場合あるということです。

まずはWindowsのファイル名ではアルファベットの大文字小文字は区別されません。TEST.TXTとtest.txtは同じフォルダ内では同一ファイルとなり、そもそも別々に保存できません。これはファイル履歴でも同じです。

ファイルエクスプローラでのファイル名順のソートはSQLサーバの照合順序(WI)に準じているようで、全角/半角で同一文字とみなされるものは同等に扱われます。全角Aと半角Aは同等ですがイコールではなく、いずれも全角B、半角Bより小さいが、全角Aは半角Aより大きいと判断され、全角/半角はファイル名としては別のものとして扱われます。TEST.TXTとTEST.TXTは別ファイルになります。ところが、ファイル履歴ではどちらか一方しか保存されません。つまり同一ファイル(イコール)とみなされます。

ファイル履歴ではファイルエクスプローラの照合順序で同等のファイル名は同一ファイルとして扱われているような感じです。照合順序の判定では最初に同一文字として扱う文字を半角大文字に統一するなどして大小比較するでしょう。ここでイコールの場合に、ファイルエクスプローラでは元の名前で比較し大小を決定しているようですが、ファイル履歴では同一ファイルとして扱っているのではないかと思います。どういう場合にエラーになるかはわかりませんが、異なるファイルを同一ファイルとして扱おうとするのだから、エラーが発生しても不思議ではありません。

理由はともあれ、あやしそうなファイルはリネームしておいた方が無難でしょう。
------------------- スクリプト -------------------

//以下のスクリプトをテキストファイルにコピーし、
//fileNameCheck.jsなどの拡張子.jsの名前で保存する。

//コマンドプロンプト(cmd.exe)で、CScriptで実行する。
//例: >CScript fileNameCheck.js

//WScriptだとEchoの度にダイアログが表示される。

//ファイル履歴の対象フォルダをプログラムで取得する方法がわからないので、
//チェックするフォルダのリストを各自の環境に合わせて設定する。
var homeDir = "C:\\Users\\UserName\\";
var folderPaths = [
    homeDir + "Desktop",
    homeDir + "Documents",
    homeDir + "Pictures",
    homeDir + "Videos",
    homeDir + "Music"
];

//半角と同じと判断される文字の全角、半角対照辞書。
//regularizeNameでzen2hanに含まれる全角文字が見つかった場合、半角文字に置き換える。
//'#'などの記号は問題ないかもしれない。
//また、ハイフン、ピリオドなどは対応する全角文字が複数あるため、適切な対応は不明。
var zen2han = {
    ' ': ' ',
    'a': 'a',
    'b': 'b',
    'c': 'c',
    'd': 'd',
    'e': 'e',
    'f': 'f',
    'g': 'g',
    'h': 'h',
    'i': 'i',
    'j': 'j',
    'k': 'k',
    'l': 'l',
    'm': 'm',
    'n': 'n',
    'o': 'o',
    'p': 'p',
    'q': 'q',
    'r': 'r',
    's': 's',
    't': 't',
    'u': 'u',
    'v': 'v',
    'w': 'w',
    'x': 'x',
    'y': 'y',
    'z': 'z',
    'A': 'a',
    'B': 'b',
    'C': 'c',
    'D': 'd',
    'E': 'e',
    'F': 'f',
    'G': 'g',
    'H': 'h',
    'I': 'i',
    'J': 'j',
    'K': 'k',
    'L': 'l',
    'M': 'm',
    'N': 'n',
    'O': 'o',
    'P': 'p',
    'Q': 'q',
    'R': 'r',
    'S': 's',
    'T': 't',
    'U': 'u',
    'V': 'v',
    'W': 'w',
    'X': 'x',
    'Y': 'y',
    'Z': 'z',
    '1': '1',
    '2': '2',
    '3': '3',
    '4': '4',
    '5': '5',
    '6': '6',
    '7': '7',
    '8': '8',
    '9': '9',
    '0': '0',
    '@': '@',
    '!': '!',
    '"': '"',
    '#': '#',
    '$': '$',
    '%': '%',
    '&': '&',
    '\': '\'',
    '(': '(',
    ')': ')',
    '=': '=',
    '-': '-',
    '~': '~',
    '^': '^',
    '|': '|',
    '\': '\\',
    '[': '[',
    ';': ';',
    ':': ':',
    ']': ']',
    ',': ',',
    '.': '.',
    '/': '/',
    '`': '`',
    '{': '{',
    '+': '+',
    '*': '*',
    '}': '}',
    '<': '<',
    '>': '>',
    '?': '?',
    '_': '_'
};

var fso = new ActiveXObject('Scripting.FileSystemObject');
//zenに含まれる文字が対象文字列にあるか判断するのに用いる正規表現。初期化時にセットする。
var regexZen;

//.jsファイルをダブルクリックするとWScriptで実行されるので、exeの名前をチェックし、WScriptの場合は警告を出して終了する。
if (/cscript\.exe$/i.test(WScript.FullName)) {
    //zenに含まれる文字が対象文字列にあるか判断するのに用いる正規表現をセットする。
    var str = "[";
    for (key in zen2han) {
        str += key;
    }
    str += "]";
    regexZen = new RegExp(str);
    //対象のフォルダを順次チェックする。
    for (var i = 0; i < folderPaths.length; i++) {
        var folderPath = folderPaths[i];
        if (fso.FolderExists(folderPath)) {
            folderCheck(fso.GetFolder(folderPath));
        } else {
            WScript.Echo("Folderなし: " + folderPath);
        }
    }
} else {
    WScript.Echo("CScriptで実行してください。")
}

function folderCheck(folder) {
    //動作確認時にコメントを外すとチェック中のフォルダ名をログ表示する。
    //WScript.Echo("FOLDER: " + folder.Path);

    //フォルダ毎に辞書を初期化。
    var nameDict = [];
    //フォルダ内のファイル名をチェック。
var files = new Enumerator(folder.Files);
    for (; !files.atEnd(); files.moveNext()) {
        var file = files.item();
        regularizeName(file, nameDict);
    }
    //サブフォルダの名前をチェックし、再帰的にfolderCheckを実行。
var subFolders = new Enumerator(folder.SubFolders);
    for (; !subFolders.atEnd(); subFolders.moveNext()) {
        var subFolder = subFolders.item();
        regularizeName(subFolder, nameDict);
folderCheck(subFolder);
}
}

//zenに含まれる文字がある場合、それに対応するhanの文字に置き換える。
function regularizeName(item, nameDict) {
    var name = item.Name, newName;
    if (regexZen.test(name)) {
        //zen2hanに含まれる全角文字が見つかった場合、対応する半角に置き換え。
        var chars = name.split('');
        for (var n = 0; n < chars.length; n++) {
            var han = zen2han[chars[n]];
            if (han) {
                chars[n] = han;
            }
        }
        newName = chars.join('').toLowerCase();
    } else {
        //zen2hanに含まれる全角文字がない場合は小文字に統一。
        newName = name.toLowerCase();
    }
    var registerdItem = nameDict[newName];
    if (registerdItem) {
        //zen2hanで全角を半角に変換した名前が既に登録済みの場合にログを出力。
        WScript.Echo("要チェック:" + item.Path + " <=> " + registerdItem.Name);
    } else {
        //まだnameDictに登録されていない場合は追加。
        nameDict[newName] = item;
    }
}

    0 件のコメント: