Showing posts with label Functions. Show all posts
Showing posts with label Functions. Show all posts

Thursday, March 3, 2011

One Approach to Logging in PowerShell

I was writing a script recently, and I realized that I was missing the ability to use the standard logging module from Python.  I decided to make a stripped-down logging function that would make logging easy, and that could be re-used.  I'd be interested to hear what approaches others have taken to solving this problem.  Here's what I came up with:



# Set severity constants
$MSG_INFORMATION = 0
$MSG_WARNING = 1
$MSG_ERROR = 2
$MSG_SEVERITY = @('Information', 'Warning', 'Error')


# Set configurable settings for logging
$LOG_LEVEL = $MSG_INFORMATION
$LOG_FILE = 'my_logfile.log'
$SMTP_TO = 'devnull@tojo2000.com'
$SMTP_SERVER = 'smtp.tojo2000.com'
$SMTP_SUBJECT = 'Script error!'


function Write-Log {
  <#
  .SYNOPSIS
     Writes a message to the Log.
  .DESCRIPTION
    Logs a message to the logfile if the severity is higher than $LOG_LEVEL.
  .PARAMETER severity
     The severity of the message.  Can be Information, Warning, or Error.
     Use the $MSG_XXXX constants.
     Note that Error will halt the script and send an email.
  .PARAMETER message
     A string to be printed to the log.
  .EXAMPLE
     Log $MSG_ERROR "Something has gone terribly wrong!"
  #>
  param([Parameter(Mandatory=$true)][int]$severity,
        [Parameter(Mandatory=$true)][string]$message)


  if ($severity -ge $LOG_LEVEL) {
    $timestamp = Get-Date -Format 'yyyy-MM-dd hh:mm:ss'
    $output = "$timestamp`t$($MSG_SEVERITY[$severity])`t$message"
    Write-Output $output >> $LOG_FILE


    if ($severity -ge $MSG_ERROR) {
      Send-MailMessage -To $SMTP_TO `
                       -SmtpServer $SMTP_SERVER `
                       -Subject $SMTP_SUBJECT`
                       -Body $output `
                       -From $SMTP_FROM
      exit 1
    }
  }
}

Sunday, January 23, 2011

Getting Performance Counters in PowerShell

I had a friend ask me recently if there was an easy way to monitor arbitrary performance counters in a script, and I didn't really have time to think about it at the time, but this weekend I decided to give it a whirl, and I came up with this function, that I'll definitely be putting in my profile at work.

The way it works is pretty straightforward, but you need to know some background about Performance Counters if you haven't used them before.  There are basically four pieces of information needed to retrieve a counter:

  • Category
  • Counter
  • Instance
  • Computer
If these don't ring a bell right away, fire up Perfmon, right-click and select Add Counters, and you'll see something like this:


In the top-left box you can see the Categories, and if you expand them you'll see the Counter names, and bottom left are the Instances.

Classic example: you want to find out the Current Disk Queue Length on your C: drive.
  • Category = 'LogicalDisk'
  • Counter = 'Current Disk Queue Length'
  • Instance = 'C:'
  • Computer = '.' (default, it means "this computer")
Using Get-PerformanceCounter, you'd do it like this:

Get-PerformanceCounter LogicalDisk 'C:' 'Current Disk Queue Length'

Or lets say you want to get all of the counters for the C: drive:

Get-PerformanceCounter LogicalDisk 'C:'



Anyway, here's the function, and you can also find it here: http://poshcode.org/2475

Wednesday, August 12, 2009

Use PowerShell to Get Local Group Members from a Remote Computer

I had a friend ask me recently how to get a list of administrators from a server.

"That's easy" I thought. "You just have to... um... actually..."

It turns out that this can be frustratingly difficult in PowerShell, so I wrote this module to make it easier when I'll need to do this in the future. It's comprised of three functions:
  • Add-NoteProperty: About 90% of the time when I want to create a custom PSObject and add some properties, they're all NoteProperties. This function makes adding a property easy, like so: Add-NoteProperty $my_object 'PropertyName' $property
  • Get-COMProperty: This is a kludgy hack to get around the fact that members of groups gotten through ADSI get returned as __ComObject objects, and you have to call the InvokeMember() static method of the class in order to get at their properties. Now I can just do this: Get-COMProperty $com_object 'PropertyName'
  • Get-LocalGroups: This is the function that we needed. It returns a list of custom PSObjects representing the local groups on a server, and each one has a property called Members that is a list of custom PSObjects for each member, including the Name, Domain, and ADSPath. From there, you can use whatever method you want to the object you want, whether it's using Get-QADUser for domain users, or whatever, for example: Get-LocalGroups computername.
If you're using Powershell v2 CTP3 or higher, you can use help on each of the functions to see examples if you forget. You can download the module from Poshcode.org here.



Friday, May 1, 2009

Advanced Functions: Using Values from the Pipeline (2.0 CTP3)

Note: The following code will only work on Powershell 2.0 CTP3 or later.

A while back I showed how you can use $input as a parameter to a function in order to use it in the pipeline. Unfortunately that technique has the side effect of making PowerShell stop and gather up all variables that are being sent down the pipeline into $Input and then passing it to the function. Ordinarily it doesn't matter much, but if you are processing a lot of data, say the results of a SQL query, you may end up using up massive amounts of memory and watch your computer grind to a halt while the memory manager swaps a billion times a second (okay, I made that number up).

The ValueFromPipeline Parameter Property

As the name suggests, this property indicates whether or not a parameter can take a value from the pipeline. Just set it to $true when declaring the variable (you'll see an example at the bottom of this post).

Doing that alone will not give you the effect you're looking for, though. It will mysteriously process the first item passed down the pipeline...and then suddenly stop. This was very frustrating when I first came across it, and left me scratching my head, until a post on the microsoft.public.windows.powershell group happened to mention the critical missing piece in an unrelated discussion.


BEGIN, END, and PROCESS Blocks

The first thing you need to know is that if you want to take a value from the pipeline and use it, then you need to add in the optional PROCESS Block. Every function essentially has a BEGIN, END, and PROCESS scriptblock in it:
  • BEGIN - This scriptblock is run the first time the function is launched
  • PROCESS - This scriptblock is run each time the function receives input
  • END - This scriptblock is run after all input has been processed
Here's the important part: explicitly declaring these blocks is optional, but if you don't use them, then the body of your function executes in the END Block. That's why it was only executing once.

As an example of a script that uses these blocks, take a look at this little ditty I whipped up for work (I highlighted the important parts):




Monday, September 1, 2008

Putting the Fun in Functions

I just wanted to make a quick post to point out two neat features of functions that I left out of the last post since it was getting a little long, piping to functions, and autocomplete for functions.


AutoComplete for Cmdlets and Functions

You're probably used to using the TAB key to autocomplete file names, but have you noticed that you can autocomplete cmdlet and function names, too?  This comes in useful a lot since I don't have all of the standard cmdlets' names memorized yet.  Just start typing the name of a function or cmdlet and hit tab.  If the name that comes up isn't what you're looking for, just keep hitting TAB and you'll cycle through the available options.

For example:

PS C:\>  out-

Will give you, if you keep tabbing:
  • Out-Clipboard
  • Out-Default
  • Out-File
  • Out-GridView (super cool, I didn't know about this one)
  • Out-Host
  • Out-Null
  • Out-Printer
  • Out-String


Piping to Functions

Piping to functions is really easy.  Anything piped to a function is automatically added to an array called $input.  You can just add a loop in your function to cycle through the values in $input and voila!

Take the following example**:

# Get-Count()
# Gets the number of objects in the input pipeline.
#
# Returns:
#   An int with the count
#

function Get-Count () {
  $i = 0;

  foreach ($obj in $input) {
    $i++;
  }

  Write-Output $i
}


** "But Tojo," you're thinking, "Doesn't Measure-Object do the same thing?"  Indeed it does, but it's much slower in my experience because it also has a lot of extra bells and whistles that I don't need if I just want to see how many lines are in a file, etc.

Friday, August 29, 2008

Functions in PowerShell


What's a function?

A function is a script block with a name.  Most functions also take parameters and/or return a value, but that's not required.  The PSDrive called function: has a list of all defined functions, and there will be quite a few in there from the first time you install PowerShell.  You can use Get-Item (or gi, for short) cmdlet to get the Definition property of the function to see how they're made, as shown below.

Function Parameters and the param() Method

Let's make a test function and try this bad boy out.  I'm going to make a function that adds two numbers.  You can use the return statement to explicitly return a value, but any expression that is not assigned to a variable will be sent as output.

function Add-Numbers ($x, $y) {
  $x + $y
}

PS Function:\>  Add-Numbers 23 77
100

Okay, that worked, so let's look at the Definition property:

Function:\>  (gi Add-Numbers).Definition
param($x, $y) $x + $y

That's funny, I don't remember using the param() method in my function.

The param() method is implied when you put your parameters in parentheses before the script block.  This code would do the same thing:

function Add-Numbers {
  param($x, $y)
  $x + $y
}

Command-Line Switches and Named Parameters

So how do we set up named parameters?  We already did.  In PowerShell every variable name that you add to the param() method also is automatically created as a named parameter that you can use at the command-line.  Observe:

Function:\>  Add-Numbers -y 10 -x 20
30

Default Parameter Values

What if you have an optional variable?  Something that usually has the same value, that you'd rather not have to type in every time?   Easy.  Just assign it a value within the param method and you're done.  If you use the parameter when you call the function, then it will take on the value you assign, but if you don't, then it will keep the default value.

function Add-Numbers {
  param($x, $y=20)
  $x + $y
}

Function:\>  Add-Numbers 203
223

A Final Note about Parameters

There are two caveats when calling a function that you should be aware of:
  1. Unnamed parameters are assumed to be in the order that they were declared in param().  If you call Add-Numbers with only one number then it will be assigned to $x because that variable came first.  
  2. Any parameters left over after being assigned to parameters get dumped into an array called $args.

An Example Incorporating What We've Learned

# Get-SmsWmi
# A wrapper for Get-WmiObject that makes it easy to get objects from SMS.
#
# Args:
#   $Class: the WMI class to retrieve
#   $Filter: the where clause of the query
#   $Computername: the SMS server hosting the SMS Provider
#   $Site: the SMS Site Code of the target site
# Returns:
#   An array of WMI objects

function Get-SmsWmi {
  param([string]$Class = $(throw "ERROR: You must enter a class name.`n"), 
        [string]$Filter = $null, 
      [string]$ComputerName = 'sms-server', 
      [string]$Site = 'S00')
  
  [string]$query = "Select * from $Class"
  
  if ($Filter) {
    $query += " Where $Filter"
  }
  
  # Now that we have our parameters, let's execute the command.
  gwmi -ComputerName $ComputerName -Namespace ('root\sms\site_' + $Site) -Query $query
}