The Johnson Blog

Ramblings of a geek with a few hobbies…

Tag: powershell

  • Copy File Path in Windows Explorer

    Update: I’ve been notified by Mark that this is completely useless.  Please disregard 🙂   In Win7 and 2008 you get the same option by just holding down Shift as you right-click.  Thanks Mark!

    Ever find yourself in Windows, browing through files and needing to copy a file’s full path?  As a developer, this seems to happen often, and I today I got tired of not having a quick way of doing this.

    A little registry tweak later, I now have a Copy Path command on my right-click menus in Windows Explorer.

    This puts the path to the selected file (or folder) in the clipboard.  Quite handy for quickly opening that file in an application without having to navigate folders or type the path.

    Download and run the Regedit file here.

    That menu option is just conigured to run the following PowerShell command:

    c:windowssystem32WindowsPowerShellv1.0PowerShell.exe -Command “‘%1’ | clip”

    Simple, yet very effective.

  • Capitalizing First Letter of Every Word with Powershell 2

    I came across the need to captalize the first letter of every word (I needed this for names, including hyphenated names) with PowerShell, and learned that PowerShell 2 has a great feature that comes in handy here.

    I first tried using the PowerShell -replace operator but you can’t do function calls or anything complex in the replacement parameter.  So $name = $name -replace 'b(w)', '$1.ToUpper()' doesn’t work.

    If I were using C#, I would just use Regex.Replace, making use of a MatchEvaluator delegate (using lambda expression syntax): name = Regex.Replace(name, 'b(w)', m => m.Value.ToUpper());

    Thankfully, in PowerShell 2 you can now use an script block as a delegate. So my problem is solved with $name = [Regex]::Replace($name, 'b(w)', { param($m) $m.Value.ToUpper() });.

    If you’re unfamiliar with the MatchEvaluator, for each regular expression match encountered, the delegate function gets called and that function’s return value is the string used as the replacement. So in my example here, 'b(w)' matches the first letter of every word. That letter gets passed to my script block as a .NET Match object, whose Value is the matched letter. The returned uppercase version of the letter is put back into the original string. Done!

  • More PowerShell – a Cmdlet

    This past weekend I spent some time reading up on and writing my first PowerShell cmdlet.   The cmdlet is an easy one, but replaces a PS function I have copy/pasted into several scripts here and there to handle cleaning out directories of old backups or other types of files.  One scenario is my RadioShark which I have setup to make daily radio recordings.  The software doesn’t have any settings for how long to keep the files, so up until now I have just been manually deleting a couple dozen at a time every month or so. 

    Enter Remove-OldItems, named after the built-in Remove-Item.

    Full Syntax: Remove-OldItems c:temp -pattern *.mp3 -KeepDays 7 -Leave 2 -Confirm -WhatIf

    That will remove all mp3 files in c:temp which are older than 7 days.  The Leave parameter is for when I use it for dealing with backup files and is a safeguard so that even if all of the files fall out of the date range, they won’t all be deleted. 

    I have put together 32-bit and 64-bit installers, feel free to download them.  If you run it on a 64-bit system, the install will register with both the 32 and 64bit PowerShells. I can make the code available if anyone’s interested.

    After installing the .msi, you can confirm the new Snapin is on the system by running get-pssnapin -registered. You should see EjiSnapin listed.After Install

    Now the Snapin containing the cmdlet(s) is there, but not loaded into the current PowerShell session.  Since I’m going to be running this from a Scheduled Task, I don’t want to have to explicitly run Add-PSSnapin EjiSnapin every time I want to use it.  One quick way around this is to add that command to the system-wide PowerShell Profile located at $pshomeprofile.ps1.  That’s easy enough, in an elevated PS prompt, just run notepad $pshome/profile.ps1 and add a single line Add-PSSnapin EjiSnapin and save the file.  Now every PS session will have the cmdlet ready to go.

    Here’s the final result, a scheduled task with a simple command being run to clear out old files in my RadioShark directory.

    task

    Overall it was fairly easy, the most difficult part came with getting the installers to work correctly between 32 and 64-bit installations.  Future enhancements, if I come into a situation where I need it, may be to add processing from the pipeline so a collection of files to be deleted could be passed to the cmdlet instead of a directory path.  Might be useful, might not.

    Here’s C# for doing the actual deletions.

    
        DirectoryInfo rootDir = new DirectoryInfo(Path);
    
        // anything older than Today minus KeepDays may be deleted
        DateTime protectionDate = DateTime.UtcNow.AddDays(-KeepDays);
    
        List candidates = rootDir.GetFiles(string.IsNullOrEmpty(Pattern) ? "*.*" : Pattern, SearchOption.TopDirectoryOnly)
            .OrderBy(f => f.LastWriteTimeUtc).ToList();
    
        if (Leave > 0)
        {
            // pop the last Leave files off the end (the most recent)
            candidates.RemoveRange(candidates.Count - Leave, Leave);
        }
    
        // now only keep those that are old enough
        candidates.RemoveAll(f => f.LastWriteTimeUtc >= protectionDate);
    
        // cycle and delete
        candidates.ForEach(f =>
            {
                if (ShouldProcess(f.FullName, "delete"))
                {
                    try
                    {
                        f.Delete();
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        WriteWarning(String.Format("Unable to delete '{0}', UnauthorizedAccess", f.FullName));
    
                        // IOException can also occur, but I want that to be a termanating exception
                    }
    
                }
                else
                {
                        // Nothing to do, ShouldProcess provided any errors/warnings/etc.
                }
            });

    In the process of writing this I learned that PowerShell V2 allows you to script cmdlets.  So this could have been greatly simplified (mostly on the deployment side), but that wouldn’t have been nearly as interesting, right? 

    That’s all folks. Thanks for reading.

  • Backups and my first foray into PowerShell

    First, some background.

    Last week I received the final pieces of my backup strategy, a pair of 500 GB hard drives that I will be rotating periodically to some undisclosed location that isn’t my house.  Right now, my fileserver makes backups and sticks them on a mirrored RAID array.  These drives are simply to hold extra copies of these backup files in the event of something Very Bad(tm) happening to our house.

    Since it will be leaving my house, I decided I wanted to make sure the data was wrapped up in a nice blanket of encryption goodness.  For ease of use, I have an eSATA docking station that allows the drives to be ‘hot-swapped’ – and for further convenience I’m attaching it to my desktop instead of the server so that it will be harder to forget to rotate the drives out (in the basement = out of sight, out of mind).

    So all of this presented an opportunity to learn the relatively-new command line environment, Microsoft PowerShell

    The scripts (feel free to download) do the following:

    1. Mount my hard drives as encrypted TrueCrypt volumes, using a keyfile.  Now anything written to the drives are wrapped in that nice encryption blanket I mentioned earlier.  Mmm… scrambled bits and blankets.
    2. Checks the amount of space available in the target directory and deletes the necessary number of old backups to make sufficient room for the new backup copies.
    3. Calls on RoboCopy (included in Windows Vista) to do the actual file copying.  Note that in my testing, I found RoboCopy to be 3-4 times as fast as xcopy across the network.
    4. Sends off a completion email
    5. Dismounts the encrypted volume

    Since this was my first use of PowerShell, I’m sure there is a lot of room for improvement and that I’ve only just scratched the surface.  That said, I learned enough about PowerShell that I’m going to keep pushing to learn more.

    A few things I like:

    • It was very easy to use several diffferent MS technologies from the one script.  I seemlessly use .NET objects, COM objects, and even pull in some WMI interaction to get the job done.
    • Finally having regular expressions available as first-class citizens in scripts – a.la perl and others.  No absolute need to explicitly fire up the .NET regex classes all the time, even though that’s definitely possible and useful too.
    • Finally not having to rely on essentially screen-scraping command outputs. Yay for objects!
    • Writing custom cmdlets in C# is pretty straightforward.  I started down this route to make my own Start-Backup cmdlet, but decided against it since I specifically wanted to learn PS scripting and not just write the solution in C#.

    A few things I dislike:

    • using -lt and -gt (and similar) instead of “<” and “>” for comparisons and equality.  It just feels wrong.
    • Lack of an official language reference – I couldn’t believe one wasn’t available, and this left me in the dark and learning by trial by error on some key behaviors of powershell (like function return values, for instance).  The “documentation pack” has a crappy quick-reference/cheat-sheet sort of thing, but not a real spec.
    • How hard it was to track down the PowerShell SDK – note: you have to download the Windows SDK and blindly choose a few installation options to get the SDK installed.
    • I made the PowerShell crash a few times.  That shouldn’t be able to happen.