// // This JavaScript program scans a directory and all // of its sub-directories for file names and directory // names that contain Unicode characters or special // characters or any illegal characters and it tries // to rename them to plain text ASCII. // // Written by Zsolt N. Perry (zsnp@juno.com) // in May 2023, Pensacola, Florida // // This JavaScript program is FREEWARE. If you want // to incorporate it or parts of it into your program, // just copy and paste whatever you need! There is no // need to ask for permission. This software is // distributed "AS IS." There is no warranty of any kind. // The author will not be held liable for any loss // resulting from the use or misuse of this software. // ////////////////////////////////////////////////// // // >>>>>>>>> SCRIPT CONFIGURATION >>>>>>>>>> // SELF = WScript.ScriptFullName; // Full path and name of this script CURDIR = cut(SELF, "\\", 0x10010); // Extract path only. DEMO_MODE = 1; // 1=Don't rename, just show what would happen. SEARCH_PATH = "C:\\EMAILS"; OUTPUT_FILE = "C:\\EMAILS\\FILES_RENAMED_DONE.TXT"; // Override the above two lines: SEARCH_PATH = CURDIR; OUTPUT_FILE = CURDIR + "\\FILES_RENAMED_DONE.TXT"; // // >>>>>>>>> PROGRAM BEGINS HERE >>>>>>>>>> // try { FSO = new ActiveXObject('Scripting.FileSystemObject'); } catch(e) { EXIT("Error: The script cannot access the file system!"); } FILELIST = []; COUNT_TOTAL = 0; COUNT_ERRORS = 0; COUNT_ILLEGAL = 0; COUNT_UNICODE = 0; COUNT_SPECIAL = 0; COUNT_DOSNAME = 0; COUNT_RENAMED = 0; ScanDIR(SEARCH_PATH); FILELIST.sort(); SUMMARY = "DIRECTORY SCAN RESULTS\r\n\r\nDIRECTORY OF " + SEARCH_PATH + "\r\n\r\nTOTAL FILES CHECKED: " + Commify(COUNT_TOTAL) + "\r\nERRORS: " + Commify(COUNT_ERRORS) + (DEMO_MODE ? "\r\nFILES WOULD BE RENAMED: " + Commify(COUNT_RENAMED) : "\r\nFILES RENAMED: " + Commify(COUNT_RENAMED) ) + "\r\nFOUND ILLEGAL NAMES: " + Commify(COUNT_ILLEGAL) + "\r\nFOUND UNICODE NAMES: " + Commify(COUNT_UNICODE) + "\r\nFOUND SPECIAL NAMES: " + Commify(COUNT_SPECIAL) + "\r\nFOUND 8+3 NAMES: " + Commify(COUNT_DOSNAME); CreateFile(OUTPUT_FILE, SUMMARY + "\r\n\r\n" + FILELIST.join("\r\n"), 1); ALERT("Detailed list of actions is saved in a file called\n" + OUTPUT_FILE + "\n\nClick OK to view summary."); EXIT(SUMMARY); ////////////////////////////////////////////////// ////////////////////////////////////////////////// ////////////////////////////////////////////////// // File | v2023.5.3 // This function returns 1 if the file name given // is made up of valid characters. It will return // 2 if the file name contains characters between // 127 and 255. This is still valid, but it's using // the extended ASCII character set. The function // will return 4 if the file name contains any // Unicode characters. These values can add up, so it // is possible the function will return 7, which would // indicate that the file name is valid and it contains // both Unicode characters and extended characters. // // If the file name is empty or if the file name is // longer than 256 characters or if the file name contains // ? * < > | then this function will return zero. // // NOTE: When calling this function, make sure that you are // providing the FILE NAME ONLY without the path! // If you include the path, the return value might be incorrect. // For example, if the path and the file name together are // longer than 256 bytes, the function will say that // it's an invalid file name! // // Usage: INTEGER = IsValidFileName(FILENAME) // function IsValidFileName(FILENAME) { FILENAME += ""; var RESULT = 1; var i, c, L = FILENAME.length; // Total length of file name cannot be zero or longer than 256 bytes. if (L == 0 || L > 256) return 0; for (i = 0; i < L; i++) { c = FILENAME.charCodeAt(i); if (c == 9) continue; // TAB is okay. if (c > 255) RESULT |= 4; // Check for Unicode characters. else if (c > 126) RESULT |= 2; // Check for extended ASCII characters. else if (c < 32 || c == 42 || c == 60 || c == 62 || c == 63 || c == 124) return 0; // Check for illegal characters. } // Make sure the file is made up of something // other than just a bunch of periods. return (/[^\.]/.test(FILENAME)) ? RESULT : 0; } ////////////////////////////////////////////////// // File | v2023.5.3 // This function returns 1 if the file name given // is a standard 8+3 MS-DOS file name. // Returns zero otherwise. Note: The file name should // not include the path. Any backslash characters // in the file name will return zero! // // Usage: INTEGER = IsDOSName(FILENAME) // function IsDOSName(FILENAME) { FILENAME += "."; var L = FILENAME.length; // Total length of file name cannot be zero or longer than 12 bytes. if (L == 0 || L > 13) return 0; var i, c, COUNT = 0, LEN = [], VALID = ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ()[]{}#!@'~`-_"; for (i = 0; i < L; i++) { c = VALID.indexOf(FILENAME.charAt(i)); // Check if character is valid. if (c < 0) return 0; // Invalid character? if (c == 0) { LEN.push(COUNT); COUNT = 0; continue; } // Period? Save number of letters so far. COUNT++; // Count number of consecutive letters } L = LEN.length; // Get number of sections. // Cannot have more than two sections : name + extension if (L == 0 || L > 2) return 0; // File name cannot be zero bytes long and cannot be longer than 8 bytes. if (L > 0) if (LEN[0] == 0 || LEN[0] > 8) return 0; // File extension cannot be longer than 3 bytes. if (L > 1) if (LEN[1] > 3) return 0; return 1; } ////////////////////////////////////////////////// // File | v2023.5.3 // This function renames a file or directory whose name // contains special characters, Unicode characters, // or invalid characters to plain text ASCII only. // // Usage: STATUS = FixFileName(FULLNAME) // function FixFileName(FULLNAME) { var FILENAME = cut(FULLNAME, "\\", 0x10101); var i, c, R = "", NEWNAME = ''; var NEED_TO_RENAME = 0; for (i = 0; i < FILENAME.length; i++) { c = FILENAME.charCodeAt(i); if (c & 0xffffff80 || c == 127 || c == 124 || c < 32) { NEED_TO_RENAME = 1; R = " "; switch (c) { case 9: R = " "; break; // Replace TAB with space case 10: R = " "; break; // Replace LF with space case 124: R = " "; break; case 127: R = " "; break; case 128: R = "~"; break; case 153: R = "~"; break; case 160: R = " "; break; case 161: R = "i"; break; case 168: R = "''"; break; case 169: R = "(C)"; break; case 174: R = "(R)"; break; case 177: R = "+-"; break; case 178: R = "^2"; break; case 179: R = "^3"; break; case 180: R = "'"; break; case 181: R = "u"; break; case 188: R = ".25"; break; case 189: R = ".5"; break; case 190: R = ".75"; break; case 199: R = "C"; break; case 215: R = "x"; break; case 241: R = "+-"; break; case 253: R = "y"; break; case 255: R = "y"; break; case 258: R = "A"; break; case 260: R = "A"; break; case 337: R = "o"; break; case 368: R = "U"; break; case 369: R = "u"; break; case 732: R = "''"; break; case 734: R = "''"; break; case 769: R = " "; break; case 779: R = "'"; break; case 8211: R = "-"; break; case 8212: R = "--"; break; case 8220: R = "''"; break; case 8221: R = "''"; break; case 8217: R = "'"; break; case 8230: R = ""; break; case 8482: R = "(TM)"; break; case 9992: R = ""; break; case 55357: R = ""; break; default: R = " "; if ((c >= 192 && c <= 198) || (c >= 256 && c <= 261)) R = "A"; else if (c >= 224 && c <= 230) R = "a"; else if (c >= 200 && c <= 203) R = "E"; else if (c >= 232 && c <= 235) R = "e"; else if ((c >= 204 && c <= 207) || (c >= 296 && c <= 305)) R = "I"; else if (c >= 236 && c <= 239) R = "i"; else if ((c >= 210 && c <= 214) || (c >= 332 && c <= 337)) R = "O"; else if (c == 240 || (c >= 242 && c <= 246)) R = "o"; else if ((c >= 217 && c <= 220) || (c >= 360 && c <= 371)) R = "U"; else if (c >= 249 && c <= 252) R = "u"; else if (c >= 262 && c <= 269) R = "C"; else if (c >= 274 && c <= 283) R = "E"; } NEWNAME += R; continue; } // Replace illegal characters if (c == 42 || c == 60 || c == 62 || c == 63) { NEED_TO_RENAME = 1; NEWNAME += " "; // Add space continue; } NEWNAME += FILENAME.charAt(i); } if (NEED_TO_RENAME) { if (NEWNAME == "") NEWNAME = "_noname"; // Remove double spaces NEWNAME = NEWNAME.replace(/[ \t\r\n\0]+/g, " "); // Remove multiple underlines and minus signs NEWNAME = NEWNAME.replace(/ [\_\-]+ /g, " - "); // Remove multiple underlines NEWNAME = NEWNAME.replace(/[\_]+/g, "_"); // Remove multiple dashes NEWNAME = NEWNAME.replace(/[\-]+/g, "-"); // Remove double periods... NEWNAME = NEWNAME.replace(/\.+/g, "."); if (DEMO_MODE) { FILELIST.push(FULLNAME + " >> " + NEWNAME); COUNT_RENAMED++; } else { if (RenameFile(FULLNAME, NEWNAME)) { FILELIST.push(FULLNAME + " >> " + NEWNAME); COUNT_RENAMED++; } else { FILELIST.push(FULLNAME + " >|> " + NEWNAME); COUNT_ERRORS++; } } } return 1; } ////////////////////////////////////////////////// // File | v2023.5.3 // This function renames a file or a directory. // It returns 1 on success or zero if an error occurred. // Usage: INTEGER = RenameFile(FULLNAME, NEWNAME) // function RenameFile(FULLNAME, NEWNAME) { var F; try { if (FSO.FileExists(FULLNAME)) { F = FSO.GetFile(FULLNAME); F.Name = NEWNAME; return 1; } if (FSO.FolderExists(FULLNAME)) { F = FSO.GetFolder(FULLNAME); F.Name = NEWNAME; return 1; } } catch (e) {} return 0; } ////////////////////////////////////////////////// // File | v2023.5.3 // This function checks a file name to see what kind of // characters it consists of... // // (The second argument is never used. It's just a // placeholder to make the code nicer / more readable // for the caller function.) // // Usage: NEWNAME = CheckFileName(FULLNAME, [TYPE]) // function CheckFileName(FULLNAME) { COUNT_TOTAL++; var FILENAME = cut(FULLNAME, "\\", 0x10101); var V = IsValidFileName(FILENAME); if (V == 0) { COUNT_ILLEGAL++; FixFileName(FULLNAME); return; } if (V & 2) { COUNT_SPECIAL++; FixFileName(FULLNAME); return; } if (V & 4) { COUNT_UNICODE++; FixFileName(FULLNAME); return; } if (IsDOSName(FILENAME)) COUNT_DOSNAME++; } ////////////////////////////////////////////////// // String | v2023.2.12 // This function splits a string into two parts // along the first occurrence of substring. // Returns the first part if CMD is 0x10. // Returns the second part if CMD is 1. // If substring is not found, returns the // original string if CMD is 0x100. // Ignores case when CMD is 0x1000. // Starts searching from end of string when CMD is 0x10000. // // Usage: cut(STR, SUB, CMD) // function cut(STR, SUB, CMD) { STR = (typeof(STR) == 'undefined' || STR == null) ? '' : STR + ''; SUB = (typeof(SUB) == 'undefined' || SUB == null) ? '' : SUB + ''; CMD = (typeof(CMD) == 'undefined') ? 0x111 : CMD | 0; var P = (CMD & 0x1000) ? ((CMD & 0x10000) ? STR.toUpperCase().lastIndexOf(SUB) : STR.toUpperCase().indexOf(SUB)) : ((CMD & 0x10000) ? STR.lastIndexOf(SUB) : STR.indexOf(SUB)); if (P < 0) return (CMD & 256) ? STR : ''; return (CMD & 16 ? STR.substr(0, P) : '') + (CMD & 1 ? STR.slice(P + SUB.length) : ''); } ////////////////////////////////////////////////// // File | v2023.5.3 // This function reads the contents of an entire // directory with all its sub-directories and // checks each file and directory name... // // Usage: ScanDIR(PATH) // function ScanDIR(PATH) { var F, FC, FullName; PATH += ""; PATH = PATH.replace(/^\s*\"/, ""); // Remove double quote prefix. PATH = PATH.replace(/\"\s*$/, ""); // Remove double quote suffix. PATH = PATH.replace(/\//g, "\\"); // Change each forward slash to backslash. PATH = PATH.replace(/\\\\/g, "\\"); // Replace double backslash with single. if (/[\|\<\>\*\?]+/.test(PATH)) return; // STOP if illegal characters found. try { F = FSO.GetFolder(PATH); for (FC = new Enumerator(F.files); !FC.atEnd(); FC.moveNext()) { FullName = FC.item(); // Read files... CheckFileName(FullName, "FILE"); } // Look into sub-directories... for (FC = new Enumerator(F.SubFolders); !FC.atEnd(); FC.moveNext()) { FullName = FC.item(); // Read sub-directories... ScanDIR(FullName); CheckFileName(FullName, "DIR"); } } catch (e) {} } ////////////////////////////////////////////////// // Displays a message box. function ALERT(MSG) { WScript.Echo(MSG); } // Terminates the program and maybe displays a message. function EXIT(MSG) { if (typeof(MSG) == "string" && MSG.length) ALERT(MSG); WScript.Quit(0); } ////////////////////////////////////////////////// // Math | v2023.4.11 // This function inserts commas into a number at // every 3 digits and returns a string. // Usage: STRING = Commify(INTEGER OR STRING) // Copied from www.PerlMonks.org/?node_id=157725 // and ported to JavaScript. // function Commify(N) { if (typeof(N) == 'undefined' || N == null) return '0'; N += ''; if (N.length == 0) return '0'; N = N.split('').reverse().join(''); N = N.replace(/(\d\d\d)(?=\d)(?!\d*\.)/g, function (a, b) { return b + ","; }); return N.split('').reverse().join(''); } ////////////////////////////////////////////////// // File | v2023.4.29 // This function creates and overwrites a file and // with a plain text string. When the third argument // is omitted, the file is saved in ASCII mode, and // if the third argument is 1, the file is saved in // Unicode text format. Return 1 on success or // zero if an error occurred. // // Usage: INTEGER = CreateFile(FILENAME, STRING, [UNICODE]) // function CreateFile(FILENAME, CONTENT, UNICODE) { UNICODE |= 0; try { F = FSO.CreateTextFile(FILENAME, 1, UNICODE); F.Write(CONTENT); F.Close(); return 1; } catch (e) {} return 0; } //////////////////////////////////////////////////