Weekend Scripter: Build Your Own PowerShell Cmdlet: Part 1 of 9

Weekend Scripter: Build Your Own PowerShell Cmdlet: Part 1 of 9

  • Comments 7
  • Likes

Summary: Microsoft Windows PowerShell MVP, Sean Kearney, begins a series of guest blogs that detail how to build your own cmdlet.

Microsoft Scripting Guy, Ed Wilson, is here. We have a special week in store for you. Guest blogger and Windows PowerShell MVP, Sean Kearney, has written a series about building cmdlets. For more about Sean, see his previous guest blog posts.

Note This is Part 1 of a nine-part series about building your own Windows PowerShell cmdlet. Read the entire series as it unfolds.

Here’s Sean…

You’ve opened up the Blue Console of wonder, the land of digital poetry we call Windows PowerShell. You’ve begun to write scripts to allow you perform your tasks with greater ease. Life feels so much better, but you still keep hitting a snag in the carpet.

One of the drawbacks to a script, just as we had with VBScript and our batch file days, is that you must specify the path to the script each time you execute it.

If you don’t like adding the path, you could dedicate a folder to all scripts in your system and add its location to the system path.

And now enter our hero: the advanced function

But Windows PowerShell opens an option that we didn’t actually have before. As of Windows PowerShell 2.0, we could create what is referred to as an “advanced function.” In many respects, an advanced function is much like a regular script except it stays loaded in memory, and it can mimic a binary cmdlet. There is one difference of course: an IT Professional can write an advanced function. It does not require the use of Visual Studio or higher-end tools. More importantly, it is not uncommon to modify an existing script into an advanced function.

Within these new cmdlets, you can even provide Help, examples for how to use them, and detailed instructions. It is not necessary to provide Help, but it is highly encouraged. Although it may not be the version of Microsoft Exchange, you should treat your advanced function like any other piece of software. Document it and provide as much Help as you can to enable your clients, even if your clients are your coworkers.

Like a script, your home-brew cmdlets can accept parameters and return objects. Plus, it can do something a little more impressive—it can sit in the pipeline and process the streams of data. For example, which of the following would you prefer?

Running something like this to process a list of new users for your company…

# Import List of new staff

 

$NewStaff=IMPORT-CSV C:\PowerShell\NewStaff.CSV

 

# Process and create accounts for new staff

 

FOREACH ($User in $NewStaff) {

            C:\PowerShell\NewUserScript.Ps1 $User.First $User.Last

}

Or does this seem simpler and more appropriate?

IMPORT-CSV C:\PowerShell\Newstaff.csv | NEW-StaffMember –first $_.First –lastname $_.Last

Of course, both examples work fine. However, the second is more seamless appearing to users, and it almost steps into the concept of what a workflow would be.

GET A LIST OF NEW STAFF

CREATE ACCOUNTS FOR NEW USERS

I tend to prefer using cmdlets over scripts for my daily tasks. It feels like a quick sentence when I hear common phrases like “NEW USER for Accounting,” “DISABLE ACCOUNT for JOHN SMITH,” or “GET REPORT for SOX.”

Writing it as a script would work fine too, but I’ll be honest. I’m lazy. I don’t want to type path names, I don’t want to type extensions…really I don’t even want to type. Honestly, if it were cost effective and safe, I would like to hook myself into the system like Johnny Mnemonic and simply THINK the work that I want done.

But because I can’t do that (and because I shudder at what my brain would do if it imagined a blue screen of death), I go for Windows PowerShell and cmdlets. It’s simpler, more cost effective, and less likely of electrical overload from experimental brain interfaces purchased on eBay.

Let’s start with a script

So how do we start? Let’s take a very simple script. We will take this script and turn it into a function, and then into an advanced function. In this way, you should be able to take existing scripts and create your own cmdlets.

This script is going to create a text file with the name Logfile with the current date as part of the file name.

# GET the Current Date for our Logfile

 

$Today=GET-DATE

 

# Define folder to store logfiles within

 

$Folder=’C:\PowerShell’

 

# Name to assign to our Logfiles

 

$Preface=’Logfile’

 

# File extension to our Logfiles

 

$Extension=’.Log’

 

# Extract the Date removing the “/”

 

$Date=$Today.toshortdatestring().Replace(“/”,””)

 

# Extract the Time removing the “:”

 

$Time=$Today.tostring(“HH:mm:ss”).Replace(“:”,”“)

 

# Build our Filename

 

$Logfilename=$Folder+"\"+$Preface+”-“+$Date+”-“+$Time+$Extension

 

# Test and ensure file does not already exist

 

IF (TEST-PATH -path $Logfilename)

 

{ WRITE-ERROR –message “Error: $Logfilename exists.” –category ‘WriteError’

 

# If file exists, return a status of Boolean $False for Unsuccessful

 

RETURN $FALSE }

 

ELSE

 

{

 

# Create logfile

 

NEW-ITEM –Type File -path $Logfilename -Force | OUT-NULL

 

# Return the Full path and filename if successful

 

RETURN $Logfilename

 

}

We’ll save this as a file name called newlogfile.ps1 in a folder called C:\PowerShell. This script will return one of two responses. If successful, it will produce a new blank log file along with the Boolean value $TRUE. If not, it will write to the Windows PowerShell error stream and return a Boolean $FALSE. It will always return the file and the folder location where it attempted to create it.

So we will run our current script in Windows PowerShell like this.

C:\PowerShell\newlogfile.ps1

This will give us either a success like so:

Image of command output

Or a failure (cue the sad trombone sound—or quickly go to Play Sad Trombone…”wah wah wahhhh…”) such as this:

Image of command output

~Sean

That is all for today. Thank you, Sean, for sharing. This is going to be a way cool Windows PowerShell series. Join us tomorrow when Sean will present Part 2. I can’t wait.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • It didn't return true when the log file gets created.

  • Good start = long way to go.

    Even though it has little to do with the purpose of the blog post, here is a suggestion for a cleaner way to generate timestamps.

    $timestamp=[datetime]::Now.ToString('MM-dd-yyyy-hhmm')

    This is all on one line and very readable.  It does not require a lot of replacements and other things because we are using the correct value and a single format string.

    I also recommend against using all uppercase.  It makes the script harder to read.  Lower case and mixed case are the normal methods.  I also prefer the older but more compact indenting style.

    One issue with this blog’s software is that it strips all formatting from code which only aggravates our ability to read it.

    Here is a link to a reformatted version which you will see is more readable.  It is also very frugal in the use of string manipulation and for a reason.

    www.designedsystemsonline.com/.../newlogfile.htm

    I have a suspicion that the big ugly error message serves a purpose other than hiding the 'False' output in visual noise.

    I suspect the next installment of this series will be even more intriguing..

  • Hi Ed, Is it possible to use code blocks in your blog articles? It would eliminate the extra cariage returns and make the scripts easier to read :).

  • @jrv

    Good point!  It's a far more elegant solution to my original one.   You can also see my pattern of thought as well when writing.  I find and solve, find and solve.

    Now you found a better way

    @joe

    My fault.  My original Word document was a little "Spacey" :)

    @poshv

    *oops!* Caught that one!

  • @jrv

    Be careful!

    On Windows Systems which are not set to en-US,  [DateTime].Tostring() in PowerShell has a differend behavior as in C# or .NET!

    In the 12 Hour PM/AM Clock Mode you cannot distinct between 1pm and 1am !

    So i suggest to use the 24 Hour Mode!

    [Code]

    $date = [DateTime]'03/12/2012 13:12:14'

    $date

    # Result is on my German Windows System: Montag, 12. März 2012 13:12:14 (Monday 12. March 2010)

    # The clock is in 24 Hour Modus! and reports 13 Hours not 1pm!

    $date.ToString("MM/dd/yyyy")

    # Result is on my German Windows System 03.12.2012 and not like expected '03/12/2012'!

    '{0:MM/dd/yyyy}' -f $Date

    # Result is on my German Windows System 03.12.2012 and not like expected '03/12/2012'!

    # Try to use CultureInfo en-US

    $enUS = New-Object System.Globalization.CultureInfo('en-US')

    Get-Date $date -format ($enUS.DateTimeFormat.ShortDatePattern)

    # Result is on my German Windows System 3.12.2012 and not like expected '3/12/2012'!

    # The leading Sero is missing ! So this is even wrong!

    # Even you have to take care about the Clock Hour Modus!

    # a Hour String hh in lower case will result in the 12 Hour AM / PM Mode!

    # a Hour String HH in upper case will result in the 24 Hour Mode!

    # one solution to get the result is to use a construct like this:

    Get-Date $date -Format "MM'/'dd'/'yyyy'-'HHmm"

    # The result is the expecteced String: '03/12/2012-1312' on my German Windows System

    # Or this:

    $date.ToString("MM/dd/yyyy-HHmm", (New-Object System.Globalization.CultureInfo('en-US')))

    # The result is the expecteced String: '03/12/2012-1312' on my German Windows System

    # So for this trouble, I prefer to create such Dates in Filenames with PowerShell like this

    Get-Date $date -Format 'MM-dd-yyyy-HHmm'

    # The result is the expecteced String: '03-12-2012-1312' on my German Windows System

    # Or more verbose for paranoics ;-)) :

    "{0:00}" -f $date.Month + '-' +  "{0:00}" -f $date.Day  + '-' +  [String]$date.Year  + '-' +  "{0:00}" -f $date.Hour + "{0:00}" -f $date.Minute

    # The hour component, is expressed as a value between 0 and 23 so this will allways the 24 Hour Modus!

    # The result is the expecteced String: '03-12-2012-1312' on my German Windows System

    [/Code]

  • @Pete - YOU keep posting that hese formats are wrong when, in fact, they are teh expected outcome of your code.  Spend some time studying how datetime formats and the formatter work.  SOme formats are always in teh culture specific format and others are specific as formatted.

    [datetime}::Today.TOString(<some format>)

    can always override culture.

    Date time formats are subtile as this will show:

    10:17 PS>([datetime]'02-01-2011 23:12:33')

    Tuesday, February 01, 2011 11:12:33 PM

    10:17 PS>([datetime]'02-01-2011 23:12:33').ToString('hh:mm:ss')

    11:12:33

    10:18 PS>([datetime]'02-01-2011 23:12:33').ToString('HH:mm:ss')

    23:12:33

    Note how using uppercase 'HH' causes us to use 24 hour format.  All of this is culture neutral.  FOr creating file names this is what we want.

    If you want the AM/PM indicator then just add the 'tt' designation.

    ([datetime]'02-01-2011 23:12:33').ToString('hh:mm:ss tt')

    Learn about time formats here: msdn.microsoft.com/.../8kb3ddd4.aspx

    Learn about universal time here: en.wikipedia.org/.../Universal_Time

    And here:  en.wikipedia.org/.../Coordinated_Universal_Time

  • Very helpful, thanks