Help Renaming MacOS Folders With Illegal Windows Characters

I’m helping a very small nonprofit use OneDrive on their Macs, and we occasionally get the problem of illegal file names, since *nix systems are more tolerant of non-alphanumeric characters. Mac sees perfectly valid file names where NTFS-centric OneDrive won’t accept them. I know the regex I want to use, and substitute those characters with underscores instead.

A friend on another forum wrote the following Folder Action for me:

But the problem is one of the users has multiple nests of folders 6-8 levels deep, with dozens and dozens of folders in each that need fixing. Folder actions, as far as my research tells me, will not work recursively.

This older article mentions using launchd, but the implementation looks arduous. I am almost inclined to just buy them copies of KM, but that money does add up over several licenses. I don't want to ask them to buy several licenses either, since it would be asking them to buy a tank when a slingshot will do the job (as an aside: TBH requiring tech support clients to buy KM doesn't seem like a terrible policy overall; so many of these things can be fixed quickly and easily through a quick macro).

In short: I need to deliver an executable shell script or Applescript or JavaScript that cleans out most of the stuff Windows won’t tolerate in a filename. Microsoft’s answer to this oft-asked questions is to basically uninstall/reinstall/reset OneDrive, but it happens too often to consider that as a viable fix. Any scripting folks have another idea for me? I'm thinking something that says "Find [these illegal characters] in directory names within [this parent folder] recursively; substitute all instances of those characters with "_". Similar to the above, but allowing for recursive modifications.

Hey @Steve_Solari,

You should be able to create an Automator Workflow with this AppleScript and have the user run it periodically.

If you want to automate that process then money will have to be spent (I think), or LaunchD may have to be investigated a bit more.

I would have no qualms about using LaunchD on my old Sierra MacBook Pro, but for Mojave and later? I'm not sure how tough it is to use with Apple's newer security system.

This script is fully recursive and will find and rename via a regular expression.

-Chris


--------------------------------------------------------
# Auth: Christopher Stone
# Prps: Many thanks to Shane Stanley for his generous help with ASObjC.
# dCre: 2017/03/15 22:10
# dMod: 2018/08/19 10:48
# Appl: AppleScriptObjC
# Task: Find Files and Folders using a RegEx Name Pattern and a Recursive Toggle without Descending into Packages.
#     :   Replace characters inconvenient to the Windows OS in OneDrive file/folder names.
# Libs: None
# Osax: None
# Tags: @ccstone, @Applescript, @Script, @ASObjC, @Recursive, @Find, @Files, @RegEx, @Prevent, @Descent, @Packages, @WindowsOS
--------------------------------------------------------
use framework "Foundation"
use scripting additions
--------------------------------------------------------

set searchPath to "~/OneDrive"

--------------------------------------------------------

set searchPath to expandTildePath(searchPath) of me
set findNameRegEx to "(?i).*[/!\\:?@8#\\$\"<>_\\(\\)\\[\\]\\{\\}7,+&*=].*"
set foundItemList to its findFilesWithRegEx:findNameRegEx inDir:searchPath searchingRecursively:true

if foundItemList ≠ {} then
   
   repeat with i in foundItemList
      set contents of i to (get alias POSIX file i)
   end repeat
   
   tell application "System Events"
      repeat with fileAlias in foundItemList
         set newFileName to (my cngStr:"(?i)[/!\\:?@8#\\$\"<>_\\(\\)\\[\\]\\{\\}7,+&*=]" intoString:"_" inString:(get name of fileAlias))
         set newFileName to (my cngStr:"__+" intoString:"_" inString:newFileName)
         set name of fileAlias to newFileName
      end repeat
   end tell
   
end if

--------------------------------------------------------
--» HANDLERS
--------------------------------------------------------
on cngStr:findString intoString:replaceString inString:dataString
   set anNSString to current application's NSString's stringWithString:dataString
   set dataString to (anNSString's ¬
      stringByReplacingOccurrencesOfString:findString withString:replaceString ¬
         options:(current application's NSRegularExpressionSearch) range:{0, length of dataString}) as text
end cngStr:intoString:inString:
--------------------------------------------------------
on expandTildePath(tildePath)
   if tildePath starts with "~" then
      return (current application's NSString's stringWithString:tildePath)'s stringByExpandingTildeInPath() as string
   else if tildePath starts with "/" then
      return tildePath
   else
      error "Something is amiss with the path sent to expandTildePath(tildePath)!"
   end if
end expandTildePath
--------------------------------------------------------
on findFilesWithRegEx:findPattern inDir:dirPath searchingRecursively:recursiveBool
   
   set fileManager to current application's NSFileManager's defaultManager()
   set sourceURL to current application's |NSURL|'s fileURLWithPath:dirPath
   
   if recursiveBool = true then
      
      set theEnumerator to fileManager's enumeratorAtURL:sourceURL includingPropertiesForKeys:{} options:((current application's NSDirectoryEnumerationSkipsHiddenFiles) + (get current application's NSDirectoryEnumerationSkipsPackageDescendants)) errorHandler:(missing value)
      
      set theURLs to theEnumerator's allObjects()
      
   else if recursiveBool = false then
      
      set theURLs to fileManager's contentsOfDirectoryAtURL:sourceURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
      
      set theURLs to theURLs's allObjects()
      
   end if
   
   set foundItemList to current application's NSPredicate's predicateWithFormat_("lastPathComponent matches %@", findPattern)
   set foundItemList to theURLs's filteredArrayUsingPredicate:foundItemList
   set foundItemList to (foundItemList's valueForKey:"path") as list
   
   return foundItemList
   
end findFilesWithRegEx:inDir:searchingRecursively:
--------------------------------------------------------

Note that the double back-slashes in the regular expressions reflect the need to escape the back-slash in AppleScript.

-Chris

Some more ideas:

The app Hazel can perform actions on folders when their contents change. But it's rather expensive.

There are also some other tools that can rename files using regex, but I suspect you want to be able to automate this?

Also, my program "Find Any File" can be used to search folder contents deeply by regex rules, and then pass the found items to another program (like a script). But you'd still have to invoke the process manually, although you can save the search and rules to a file that'll auto-start the search once you double click it.

Thanks for the extra ideas. I tried Transnomino and it worked like a charm. Lightning fast and did exactly what I needed.

Very spiffy for a freebie! The Dev deserves a tip.

I favor Name Mangler AND A Better Finder Rename for this sort of chore, but they are commercial utilities and cost $19.00 and $24.95 respectively.

I think I've used ABFR for over 20 years now and NM for nearly a decade.

-Chris

Oh yeah. I also have a Keyboard Maestro macro which is my go-to renamer – although it only handles the items selected in the Finder.

If it won't do the job I usually jump to Name Manger, and if NM won't do it I'll trot out ABFR.

-Chris