PowerShell with a Purpose Blog

The Power of the Pipeline: Deleting Annoying Files

My dirty little secret, which I guess isn't going to be so secret now, is that I'm a closet Mac user. I love Windows Server as an OS, but I have a tendency to fiddle with Windows client OSs, and that winds up breaking them. By using a Mac, I'm completely disinclined to fiddle, and so I get more work done and have to reinstall my operating system less often (well, "never" thus far). Anyway, one of the only things I dislike about the Mac OS is the way it stores file metadata. Rather than using an alternate "stream" of a file, as NTFS does, Mac OS insists on writing "resource files." So if you have a file named Whatever.txt, there's also a hidden whatever._txt file floating around. The problem is that when you copy Whatever.txt to a USB flash drive to share with someone, they also see the useless-to-them whatever._txt file. When the contents of that USB drive are, say, abunch of training videos that you sell, it's kind of unprofessional to have all those _hidden files on there.

There's actually a neat Mac OS X tool you can buy that will automatically strip the files and/or folders, but I'm not a big fan of "buy" when it comes to software tools. Especially when I have PowerShell! I just wrote myself a one-liner - not a script, mind you (I used to have a multi-line VBScript that did this).

PS H:\> dir -recurse | where { $_.gettype().name -eq 'DirectoryInfo' } | % { $_.getfiles() } | where { $_.name -like '._*' }  | % { del -path $_.fullname -force }

Goodness, I love the pipeline. Here's what's happening:
  • I start by getting a directory - including the -recurse switch, which enumerates subfolders. Notice that I started on the H: drive, which was a USB drive I wanted to clean up. So I'm getting every file, and every folder.
  • Dir will return both folders (directories) and files. In this example, I want to clean directories that start with _, so the next step it to filter out all objects that do not have a type of DirectoryInfo (files would have a type of FileInfo). I'm looking at each object returned by Dir, and executing each one's GetType() method to return its type name. If the type name is equal to DirectoryInfo, it stays; otherwise, it gets dropped.
  • % is an alias to the ForEach-Object cmdlet, and it allows me to execute some operation for each object that remains in the pipeline. So, for each directory - which is all the pipeline will contain at this point - I'm executing the GetFiles() method. That will return a collection of files within that folder. Those get put into the pipeline; after the ForEach-Object cmdlet executes, the pipeline will now contain file objects, and not directory objects.
  • Next, I filter to that I'm only keeping those files whose names end with "._*". Those are the hidden files.
  • I pipe those hidden files to ForEach-Object again, executing the Del command (well, that's an alias to Remove-Item), giving it the full name of the file and adding the -force parameter for good measure (some of those files will be marked read-only, and that forces Del to kill them anyway). 
It's a neat trick - one that took some experimenting. Why did I first filter for directories instead of simply filtering for files? By default, Dir won't return hidden files, but a directory's GetFiles method will do so. So first I get all the directories, then ask them to give me a list of ALL their files - including hidden ones. I then filter on the file's names, getting the ones that match the pattern I'm after. I can change that pattern easily enough to look for other types of files - the example here was for a specific need, but it could easily be altered for others.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish