Your basic ITPro blog... What's going on at work, what I'm interested in.

Monday, June 30, 2008

File Management in PowerShell

Biggest files? Oldest files? Duplicates?

I am guessing that, at one time or another, most of us have asked these questions of our file stores. Answers to these questions help us make informed decisions regarding storage management, archiving, etc. I know there are 3rd party apps out there that do this sort of thing. We have Storage Exec installed on an older server. It have a lot of functionality (much of which we don't use). Mainly, we use it to ask these simple questions. I thought, why pay money for something when you don't have to? Why install another app on your file server?

To that end, I am currently working on three PoSH scripts (functions) that will answer these questions. I pre-load these scripts in my PowerShell profile so that these functions are available just like cmdlets. It keeps them handy.

The first one I want to share is: Get-BigFiles

This function is pretty straightforward and simple. You can pass it a path, quantity in results, and minimum file size. It has some simple formatting to make the output (currently to console) a bit easier to read. It also reports back the time it took to run. I consider this version 0.10

Some things that are in my head include:

  • Would using custom objects or arrays to store results make things easier to manage/report on/output/etc.? Other techniques?
  • Want to be able to output to a file (HTML or TXT, for example).
  • As I have stated before, I still think primarily in terms of VB. How would this script benefit from a more PowerShell-native approach?
  • What other features would this script benefit from?

As of now, this script functions. It gives me the information I am asking of it. In that, it is successful. It may not be the most elegant, most 'correct' way of doing things. But, if you get the answer you need and are then able to move forward doing your job, then you have a tool of worth. I welcome comments and suggestions.

###################################################################
# Function: Get-BigFiles
# 
# Description: Generates a list of files meeting the PATH,
#              QUANTITY, and SIZE criteria
#
# Input: $Path as string        ( example: "C:\windows" )
#        $howMany as integer    ( example: 20 )
#         $minSize as Integer64    ( example: 100MB )
#
# Usage:     Get-BigFiles "C:\scripts" 5 10MB
#            Get-BigFiles -howMany 20
#
# Recursively returns the largest files meeting criteria
###################################################################
function Get-BigFiles
(    [string]$Path = (Get-Location),
    [int]$howMany = 10,
    [long]$minSize = 1GB
)
{
    if (Test-Path $Path) {
        Write-Host "`n`nChecking for the $howMany largest file(s) under the `'$Path`' folder."
        if ($minSize -lt 1KB) {
            Write-Host "Minimum file size in report: NONE"
        }
        elseif ($minSize -lt 1MB) {
            Write-Host "Minimum file size in report: "($minSize / 1KB) "KB"
        }
        elseif ($minSize -lt 1GB) {
            Write-Host "Minimum file size in report: "($minSize / 1MB) "MB"
        }
        else {
            Write-Host "Minimum file size in report: "($minSize / 1GB) "GB"
        }
        $startTime = Get-Date
        Write-Host "  Start: $startTime"
        Write-Host "Depending on folder size, this could take a while..."
        $results = (Get-ChildItem $Path -Recurse |
                    where {$_.PSIsContainer -eq $FALSE -and $_.Length -ge $minSize} |
                    Sort-Object Length -Descending |
                    Select-Object     Directory,
                                    Name,
                                    Length,
                                    LastAccessTime -First $howMany
                    )
        if ($results) {
            foreach ($file in $results) {
                if ($file.Length -lt 1KB) {
                    $formattedLength = $file.Length.toString() + " Bytes"
                }
                elseif ($file.Length -lt 1MB) {
                    $formattedLength = [string]::Format("{0:#.##}",($file.Length / 1KB)) + " KB"
                }
                elseif ($file.Length -lt 1GB) {
                    $formattedLength = [string]::Format("{0:#.##}",($file.Length / 1MB)) + " MB"
                }
                else {
                    $formattedLength = [string]::Format("{0:#.##}",($file.Length / 1GB)) + " GB"
                }
                
                Write-Host "`nDirectory:   " $file.Directory
                Write-Host "Name:        " $file.Name
                Write-Host "Length:      " $formattedLength
                Write-Host "Last Access: " $file.LastAccessTime
            }
        }
        else {
            Write-Host "No files meet the minimum criteria."
        }
    }
    else {
        Write-Host "`'$Path`' is an invalid path."
    }
    $endTime = Get-Date
    Write-Host "`n  End: $endTime"
    Write-Host "`nDuration: " ($endTime - $startTime).Duration()
}

Tuesday, June 24, 2008

Freelance Web Development

I am putting a call out for my father-in-law...

I am almost embarrassed to show you what he has now, but...

http://www.classicarts.us/

He has seen this site and likes it, though I'm not sure it's much better...

http://www.golfcartconnection.com/

Anyway, he is looking for something not too complicated, but gives ample information about his business, offerings, products and services.

If anyone knows of any web developers interested in work, please pass them my e-mail address: derekDOTmangrumATgmailDOTcom

Thanks for the ear.

D

2008-06-24 PowerShell Exercise

Today we dabble with the Registry...

Of course, I don't have to warn you, use caution when messing with the Registry!

And, with that out of the way, let's look at our Hey, Scripting Guy! question for the day.

QUESTION:

Hey, Scripting Guy! I am trying to change a registry value that has the name (Default). I can change any registry value that has a “real” name, but I can’t figure out how to change one with the name (Default). If I export the value the name shows up as @, but that doesn’t work in my script, either. How do I change a registry value named (Default)?
-- TL

SCRIPTING GUY ANSWER:

   1: Const HKEY_CURRENT_USER = &H80000001
   2:  
   3: strComputer = "."
   4:  
   5: Set objRegistry = GetObject("winmgmts:\\" & strComputer & "\root\default:StdRegProv")
   6:  
   7: strKeyPath = "ScriptCenter"
   8: strValueName = ""
   9: strValue = "http://www.microsoft.com/technet/scriptcenter/default.mspx"
  10:  
  11: objRegistry.SetStringValue HKEY_CURRENT_USER, strKeyPath, strValueName, strValue

MY ANSWER(S):

As we see, the VB solution is not too complicated... only seven lines of actual code. But, this is a great question that allows us to explore the power of PowerShell as it relates to the Registry. You see, the cool thing is, PowerShell pretty much treats the Registry as any other file system. So, you can use commands like:

  • dir, ls (Get-ChildItem)
  • cd (Set-Location)
  • md, rd (New-Item, Remove-Item)

Using these, along with the various <verb>-ItemProperty cmdlets, we can easily navigate the Registry and add/remove/change/copy keys and values.

So, to address the question and solution listed above, we find the following PowerShell one-liners very handy:

First off, I didn't have the key they are referring to, namely "ScriptCenter" in HKEY_CURRENT_USER. So, let's create that:

New-Item -Path "HKCU:\" -Name "ScriptCenter"

Now, to set '(Default)' with an actual value:

Set-ItemProperty -Path "HKCU:\ScriptCenter" -Name "(Default)" -Value "New Value"

You can verify that this is set with:

Get-ItemProperty -Path "HKCU:\ScriptCenter" -Name "(Default)"

Or, to see all of the values in the key, simply leave off the -Name:

Get-ItemProperty -Path "HKCU:\ScriptCenter"

Of course, all of these one-liners can be shortened further by using aliases, positional values, etc. But, I like complete listings for these examples. So, have fun (WITH CARE) exploring the registry using PowerShell!

Sunday, June 22, 2008

Visited by royalty!

WOW!

I am not sure what I did to deserve such attention...

Here are my guesses:

  • He got a hit on his Google Alert, "Floundering goofballs trying to use PowerShell"
  • He got a headache seeing my butchering of PowerShell
  • He found my meager attempts at PoSH scripting cute and humorous, like a child role-playing. "Look how cute he is... trying to use PowerShell! That's so adorable, someone get a camera!"

:-)  :-)  :-)

I am amazed, first, that The PowerShell Guy, MoW, would ever come across my blog. Second, that he would bother actually responding to one of my PowerShell posts.

I have been flying high all day! My wife, who isn't particularly technical (though she is very gracious when I talk tech) got to hear all about how one of the "Towers of PowerShell" visited my little blog and actually responded to one of my posts! I was like a kid on Christmas!

I was being silly up above but, in reality, this is what I love about blogging and online communities! A little guy like me can interact with (and even get a little one-on-one training from) leaders in the community. There is no Ivory Tower... no untouchables! It is a huge testament to these leaders that they are so willing to impart their knowledge and be so active (and INTERactive) with everyone else.

So thanks, MoW, for visiting! You made my day!

P.S. As for your solutions... AWESOME!

Saturday, June 21, 2008

Fantastic Read!

This isn't IT related, but I feel compelled to share...

I just finished reading "Miracles on the Water" by Tom Nagorski. I don't remember how I heard about this book, but I am glad that I did.

This book tells the amazing, inspiring, and extremely tragic tale of the people onboard the doomed SS City of Benares, a British liner bound for Canada and torpedoed my a German U-Boat. Among the 400+ people on board are 90 children, ages 5-15, sent by their families and government to the Americas to escape the ruthless onslaught of the Nazis.

I have no desire to re-tell the story here. I simply want to say... Read This Book! It is 320 pages you will not regret!

Friday, June 20, 2008

2008-06-20 - PowerShell Exercise

In today's Hey, Scripting Guy!, we tackle file renaming. PowerShell is great at this sort of thing.

The Question:

Hey, Scripting Guy (and the $12 million-a-year Scripting Editor)! Is there a way to mass-rename all the files in a folder, changing the letter case as needed?
-- MV

This example converts all filenames to lower case.

The Scripting Guys' Answer

   1: strComputer = "."
   2:  
   3: Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
   4:  
   5: Set colFileList = objWMIService.ExecQuery _
   6:     ("ASSOCIATORS OF {Win32_Directory.Name='C:\Test'} Where " _
   7:         & "ResultClass = CIM_DataFile")
   8:  
   9: For Each objFile In colFileList
  10:     strName = LCase(objFile.FileName)
  11:     strExtension = LCase(objFile.Extension)
  12:  
  13:     strNewName = objFile.Drive & objFile.Path & strName & "." & strExtension
  14:     objFile.Rename(strNewName)
  15: Next

My PowerShell Answer

dir | foreach {Rename-Item $_ -NewName $_.Name.ToLower()}

They had a second example that converted the first letter of each word to upper case. I didn't come up with a way to do this too much easier than the VB way... But then, I still tend to think of things in VB terms, which isn't too great when working in PowerShell. I am still working on changin' my ways of thinkin'

Thursday, June 12, 2008

Logging Logon Events from Within the Logon Script

We have been having issues (sporadic, but annoying) with people not getting drive mappings. Drives are being mapped by our logon script, assigned via GPO.

We have identified that, in some instances, drives are not mapped because people are not actually logging in. This is especially true for our laptop users. I myself am a culprit, as I usually go in to 'Stand By', rather than shutting down.

But, the annoying part comes when people do log in but still don't get their drives.

To help troubleshoot this, I have added some logging code to the logon script. So now each time the logon script runs (presumably triggered by a user logon event), the logon script does the following:

  • maps appropriate drives for the user (example: U:\, S:\, G:\, R:\)
  • logs these events to a .txt file
    • checks to see if a txt file named <username>.txt exists
      • if not, it creates the file
    • Appends and entry
      • date and timestamp the entry
      • logs OK, FAIL, SKIP for each drive mapping

What I end up with is a text file for each user account with entries that look like:
(I removed username and computername)

START RUN: 6/12/2008  9:29:11 AM
<username> on computer <computername>
   U: map OK
   G: map OK
   S: map OK
   R: map SKIP
END RUN:   6/12/2008  9:29:13 AM
--------------------------------

My hope is that this will do a few things. First, it will give me a record of each login script run for each user. Second, it will report either OK, SKIP, for FAIL for each drive map attempt. This should give me better information to work with as we troubleshoot logon script/drive mapping issues.

Additional Info

My photo
email: support (AT) mangrumtech (DOT) com
mobile: 480-270-4332