Friday Fun Get Content Words

Recently I was tracking down a bug in script for a client. The problem turned out to be a simple typo. I could have easily avoided that by using Set-StrictMode, which I do now, but that’s not what this is about. What I realized I wanted was a way to look at all the for “words” in a script. If I could look at them sorted, then typos would jump out. At least in theory.

My plan was to get the content of a text file or script, use a regular expression pattern to identify all the “words” and then get a sorted and unique list. Here’s what I came up with.

Function Get-ContentWords {

[cmdletbinding()]

Param (
[Parameter(Position=0,Mandatory=$True,
HelpMessage="Enter the filename for your text file",
ValueFromPipeline=$True)]
[string]$Path
)

Begin {
    Set-StrictMode -Version 2.0
   
    Write-Verbose "Starting $($myinvocation.mycommand)"
   
    #define a regular expression pattern to detect "words"
    [regex]$word="\b\S+\b"
}

Process {

    if ($path.gettype().Name -eq "FileInfo") {
      #$Path is a file object
      Write-Verbose "Getting content from $($Path.Fullname)"
      $content=Get-Content -Path $path.Fullname
    }
    else {
        #$Path is a string
        Write-Verbose "Getting content from $path"
        $content=get-content -Path $Path
    }

    #add a little information
    $stats=$content | Measure-Object -Word
    Write-Verbose "Found approximately $($stats.words) words"

    #write sorted unique values
    $word.Matches($content) | select Value -unique | sort Value
 }
 
End {
    Write-Verbose "Ending $($myinvocation.mycommand)"
    }
   
} #close function

The function uses Get-Content to retrieve the content (what else?!) of the specified file. At the beginning of the function I defined a regular expression object to find “words”.

#define a regular expression pattern to detect "words"
[regex]$word="\b\S+\b"

This is an intentionally broad pattern that searches for anything not a space. The \b element indicates a word boundary. Because this is a REGEX object, I can do a bit more than using a basic -match operator. Instead I’ll use the Matches() method which will return a collection of match objects. I can pipe these to Select-Object retrieving just the Value property. I also use the -Unique parameter to filter out duplicates. Finally the values are sorted.

$word.Matches($content) | select Value -unique | sort Value

The matches and filtering are NOT case-sensitive, which is fine for me. With the list I can see where I might have used write-host instead of Write-Host and go back to clean up my code. Let me show you how this works. Here’s a demo script.

#Requires -version 2.0

$comp = Read-Host "Enter a computer name"

write-host "Querying services on $comp" -fore Cyan
$svc = get-service -comp $comp

$msg = "I found {0} services on $comp" -f $svc.count
Write-Host "Results" -fore Green
Write-Host $mgs -fore Green

The script has some case inconsistencies as well as a typo. I’ve dot sourced the function in my PowerShell session. Here’s what I end up with.

For best results, you need to make sure there are spaces around commands that use the = sign. But now I can scan through the list and pick out potential problems. Sure, Set-StrictMode would help with variable typos but if I had errors in say comment based help, that wouldn’t help. Maybe you’ll find this useful in your scripting work, maybe not. But I hope you learned a few things about working with REGEX objects and unique properties.

Download Get-ContentWords and enjoy.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

Posted in Friday Fun, PowerShell v2.0, Scripting | Tagged , , , , , | Leave a comment

PowerShell in a Nutshell

This past weekend I did an online presentation for a friend of mine who teaches for ITT in Omaha, Nebraska. He wanted me to do a brief talk about what PowerShell is and show how to use it, especially for managing Active Directory. I probably went much longer than I needed but everyone seemed to get a lot out of it. The session was recorded via WebEx. I then transcoded the recording so I could get it up on YouTube. That’s why the screen layout is a little funky and the audio is far from perfect. Still, I hope you find it useful. The presentation has a some slides but is primarily demo, including using the Microsoft Active Directory cmdlets.

You can also download my presentation and a zip filewith my demo scripts.

If you are ready to learn more check out some of the books and training videos in the side bar. Or bring me in to run a private PowerShell class for your organization. Good Luck and Enjoy!

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

Posted in PowerShell v2.0, Scripting, Training, Windows Server, WMI | Tagged , , | Leave a comment

Friday Fun: A PowerShell Alarm Clock

Today’s Friday Fun is a continuation of my exploration of ways to use Start-Job. A few weeks ago I wrote about using Start-Job to create “scheduled” tasks. I realized I could take this further and turn this into a sort of alarm clock. The goal is to execute at command at a given time, but I wanted to make it easy to specify the time. What I have so far is a function called New-Alarm. I have some other ideas and hope to expand this into a module, but for now I thought I’d toss this out to you and get some feedback.

Function New-Alarm {

[cmdletbinding(SupportsShouldProcess=$True,DefaultParameterSetName="Time")]

Param (
[Parameter(Position=0,ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullorEmpty()]
[string]$Command="Notepad",
[Parameter(Position=1,ValueFromPipelineByPropertyName=$True,ParameterSetName="Time")]
[ValidateNotNullorEmpty()]
[Alias("time")]
[datetime]$Alarm=(Get-Date).AddMinutes(5),
[Parameter(ValueFromPipelineByPropertyName=$True,ParameterSetName="Seconds")]
[int]$Seconds,
[Parameter(ValueFromPipelineByPropertyName=$True,ParameterSetName="Minutes")]
[int]$Minutes,
[Parameter(ValueFromPipelineByPropertyName=$True,ParameterSetName="Hours")]
[int]$Hours,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[Alias("init","is")]
[string]$InitializationScript
)

Process {

if ($seconds) {$Alarm=(Get-Date).AddSeconds($seconds)}
if ($minutes) {$Alarm=(Get-Date).AddMinutes($minutes)}
if ($Hours) {$Alarm=(Get-Date).AddHours($hours)}

Write-Verbose ("{0} Creating an alarm for {1} to execute {2}" -f (Get-Date),$Alarm,$Command)

#define a scriptblock that takes parameters. Parameters are validated in the
#function so we don't need to do it here.
$sbText=@"
    Param ([string]`$Command,[datetime]`$Alarm,[string]`$Init)
   
    #define a boolean flag
    `$Done=`$False
   
    #loop until the time is greater or equal to the alarm time
    #sleeping every 10 seconds
    do  {
        if ((get-date) -ge `$Alarm) {
          #run the command
          `$ActualTime=Get-Date
          Invoke-Expression `$Command
          #set the flag to True
          `$Done=`$True
          }
        else {
         sleep -Seconds 10
    }
    } while (-Not `$Done)
   
    #write an alarm summary object which can be retrieved with Receive-Job
    New-Object -TypeName PSObject -Property @{
      ScheduledTime=`$Alarm
      ActualTime=`$ActualTime
      Command=`$Command
      Initialization=`$Init
    }
"
@

#append metadata to the scriptblock text so they can be parsed out with Get-Alarm
#to discover information for currently running alarm jobs

$meta=@"

#Alarm Command::$Command
#Alarm Time::$Alarm
#Alarm Init::$InitializationScript
#Alarm Created::$(Get-Date)

"
@

#add meta data to scriptblock text
$sbText+=$meta

Write-Debug "Scriptblock text:"
Write-Debug $sbText
Write-Debug "Creating the scriptblock"

#create a scriptblock to use with Start-Job
$sb=$ExecutionContext.InvokeCommand.NewScriptBlock($sbText)

Try {
    If ($InitializationScript) {
        #turn $initializationscript into a script block
        $initsb=$ExecutionContext.InvokeCommand.NewScriptBlock($initializationscript)
        Write-Verbose ("{0} Using an initialization script: {1}" -f (Get-Date),$InitializationScript)
    }
    else {
        #no initialization command so create an empty scriptblock
        $initsb={}
    }
   
    #WhatIf
    if ($pscmdlet.ShouldProcess("$command at $Alarm")) {
        #create a background job
        Start-job -ScriptBlock $sb -ArgumentList @($Command,$Alarm,$InitializationScript) -ErrorAction "Stop" -InitializationScript $Initsb
        Write-Verbose ("{0} Alarm Created" -f (Get-Date))
    }
}

Catch {
    $msg="{0} Exception creating the alarm job. {1}" -f (Get-Date),$_.Exception.Message
    Write-Warning $msg
}
} #Process

} #end function

The function includes full help.

To use the function you specify a command string to execute at a given time. The default’s are to run Notepad in 5 minutes. You can either specify an exact time.

PS C:\> new-alarm "get-process | out-file c:\work\noonprocs.txt" -alarm "12:00PM"

Or X number of seconds, minutes or hours.

PS C:\> $s='$f=[system.io.path]::GetTempFilename(); "Hey! Are you paying attention??" > $f;start-process notepad $f -wait;del $f'
PS C:\> new-alarm $s -minutes 15 -verbose

The first command defines a command string, $s. This creates a temporary file, writes some text to it, displays it with Notepad and then deletes it. The second command creates a new alarm that will invoke the expression in 15 minutes.

For now, the command is passed as text. This is so that I can create an internal scriptblock. I use a Do loop to compare the current time to the alarm time. When the time is right, the command string is executed using Invoke-Expression.

$sbText=@"
    Param ([string]`$Command,[datetime]`$Alarm,[string]`$Init)
   
    #define a boolean flag
    `$Done=`$False
   
    #loop until the time is greater or equal to the alarm time
    #sleeping every 10 seconds
    do  {
        if ((get-date) -ge `$Alarm) {
          #run the command
          `$ActualTime=Get-Date
          Invoke-Expression `$Command
          #set the flag to True
          `$Done=`$True
          }
        else {
         sleep -Seconds 10
    }
    } while (-Not `$Done)
   
    #write an alarm summary object which can be retrieved with Receive-Job
    New-Object -TypeName PSObject -Property @{
      ScheduledTime=`$Alarm
      ActualTime=`$ActualTime
      Command=`$Command
      Initialization=`$Init
    }
"
@

I also add some metadata to the script block which gets written as the job’s result.

#append metadata to the scriptblock text so they can be parsed out with Get-Alarm
#to discover information for currently running alarm jobs

$meta=@"

#Alarm Command::$Command
#Alarm Time::$Alarm
#Alarm Init::$InitializationScript
#Alarm Created::$(Get-Date)

"@

#add meta data to scriptblock text
$sbText+=$meta

Write-Debug "Scriptblock text:"
Write-Debug $sbText
Write-Debug "Creating the scriptblock"

#create a scriptblock to use with Start-Job
$sb=$ExecutionContext.InvokeCommand.NewScriptBlock($sbText)

Finally, the alarm function allows for an initialization command, like you might use with Start-Job. This permits you to run commands such as importing modules or dot sourcing scripts. I have a function that displays a VB style message box. Here’s how I might use it as an alarm job.

PS C:\> new-alarm "get-messagebox 'It is time for that thing' -title 'Alert!'" -init ". c:\scripts\get-messagebox.ps1" -min 5

In 5 minutes the alarm will go off and I’ll get this.

Remember, the function is creating new jobs with the Start-Job cmdlet. Which means I can get job results.

PS C:\> receive-job 7 -keep

Initialization : . c:\scripts\get-messagebox.ps1
ActualTime     : 1/20/2012 8:47:07 AM
ScheduledTime  : 1/20/2012 8:47:06 AM
Command        : get-messagebox 'It is time for that thing' -title 'Alert!'
RunspaceId     : d3461b78-11ce-4c84-a8ab-9e3fcd482637

What do you think? As I said, I have a few more ideas and there are certainly a few tweaks I can make even to this code. I’ve added my Get-MessageBox function in case you want to toy with that. Download AlarmScripts.zip and let me know what you think.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

Posted in Friday Fun, PowerShell, PowerShell v2.0 | Tagged , , , | Leave a comment

Using Types with Imported CSV Data in PowerShell

The Import-CSV cmdlet in PowerShell is incredibly useful. You can take any CSV file and pump objects to the pipeline. The cmdlet uses the CSV header as properties for the custom object.

PS S:\> import-csv .\testdata.csv


Date    : 1/18/2012 6:45:30 AM
Name    : Data_1
Service : ALG
Key     : 1
Size    : 25

Date    : 1/18/2012 2:17:30 AM
Name    : Data_2
Service : AppIDSvc
Key     : 2
Size    : -30
...

But there is a downside: all of the properties are strings.

PS S:\> import-csv .\testdata.csv | get-member


   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Date        NoteProperty System.String Date=1/18/2012 6:45:30 AM
Key         NoteProperty System.String Key=1
Name        NoteProperty System.String Name=Data_1
Service     NoteProperty System.String Service=ALG
Size        NoteProperty System.String Size=25

The means some tasks such sorting or filtering will fail. But there are ways to get around this limitation. One way is to use an expression to cast a property to a different type. For example, I want to sort my test data on the Date property, but it needs to be a [DateTime] object to sort properly. Here’s how:

PS S:\> import-csv testdata.csv  | sort @{expression={$_.date -as [datetime]}} | Select Date,Name,Size

Date                       Name                       Size
----                       ----                       ----
1/9/2012 6:28:30 PM        Data_25                    26
1/11/2012 11:13:30 AM      Data_20                    44
1/11/2012 6:28:30 PM       Data_23                    33
1/13/2012 12:13:30 AM      Data_16                    42
1/13/2012 4:45:30 PM       Data_24                    47
...

My output object properties are all still strings. All I did was cast the Date property in the Sort expression. Here’s an example using filtering.

PS S:\> import-csv testdata.csv  | where {($_.date -as [datetime]) -le ("1/12/2012" -as [datetime])} | Select Date,Name,Size

Date                       Name                       Size
----                       ----                       ----
1/11/2012 11:13:30 AM      Data_20                    44
1/11/2012 6:28:30 PM       Data_23                    33
1/9/2012 6:28:30 PM        Data_25                    26

These examples are only producing results. More likely I want to import the CSV file as typed objects. Assuming you know in advance the property names and what types you want to use, here’s how you could achieve this.

PS S:\> $data=import-csv testdata.csv | Select @{Name="Date";Expression={[datetime]$_.Date}}, Name,Service,@{Name="Key";Expression={[int32]$_.Key}},@{Name="Size";Expression={[int32]$_.Size}}

I imported my CSV file and piped it to Select-Object, using hash tables to redefine the properties with appropriate types. Import-CSV writes a PSCustomObject to the pipeline anyway so using Select-Object has no effect other than giving me typed properties.

PS S:\> $data | get-member


   TypeName: Selected.System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Date        NoteProperty System.DateTime Date=1/18/2012 6:45:30 AM
Key         NoteProperty System.Int32 Key=1
Name        NoteProperty System.String Name=Data_1
Service     NoteProperty System.String Service=ALG
Size        NoteProperty System.Int32 Size=25

Now I can use $data objects anyway I want.

PS S:\> $data | where {$_.size -ge 40 -AND $_.key -le 10}


Date    : 1/17/2012 11:57:30 PM
Name    : Data_3
Service : Appinfo
Key     : 3
Size    : 42

I’m working on something that takes this idea to the next level but it isn’t quite ready for prime time. But I hope this will help manage imported objects a bit more efficiently and let you really take advantage of the PowerShell pipeline.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

Posted in PowerShell v2.0, Scripting | Tagged , , , , , | 2 Comments

Friday Fun: Output to 2 Places in 1

Today’s Friday Fun comes out of a short exchange I had yesterday with Hal Rottenberg on Google Plus. We were playing around with piping a PowerShell command to Clip.exe which dumps the output to the Windows Clipboard. I got to thinking about taking this a step further based on my needs as a writer. Often I’d like to see the results of a command and then copy and paste the results into whatever I’m working on. In other words, I need to TEE the output to two places.

PowerShell has a cmdlet called Tee-Object that follows this principal. The default behavior is to write output to the pipeline AND send it to a text file.

PS C:\> get-service | tee c:\work\svc.txt

I’ll see the results and save them to a text file. I can also use this cmdlet to save results to a variable.

PS C:\> get-service | tee c:\work\svc.txt

Status   Name               DisplayName
------   ----               -----------
Running  AeLookupSvc        Application Experience
Stopped  ALG                Application Layer Gateway Service
Stopped  AppIDSvc           Application Identity
Stopped  Appinfo            Application Information
...
Running  wudfsvc            Windows Driver Foundation - User-mo...
Stopped  WwanSvc            WWAN AutoConfig


PS C:\> $svc.count
196
PS C:\>

One approach I came up with to incorporate with Clip.exe was this:

PS C:\> get-service | tee -Variable svc | clip

I don’t get the results immediately to the screen; they are saved to the variable. But at the same time output has been directed to the Windows Clipboard. That could be useful. But you know me, I always have to tinker a bit more and I ended up with a function called Out-Tee.

Function Out-Tee {

[cmdletbinding()]

Param (
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[object[]]$InputObject,
[alias("foregroundcolor","fg")]
[string]$TextColor=$host.ui.rawui.ForegroundColor
)

Begin {
 #define an empty array to hold piped in objects
 $a=@()
}

Process {
    #add each piped in object to the array
    $a+=$inputobject
}

End {
    #write the array to the pipeline as a string then pass to Write-Host
    $a | out-string | write-host -fore $textColor
    #write the array again to Clip.exe
    $a | clip
}

} #end function

This simple function takes a PowerShell expression and writes the results to the console using Write-Host and also to the clipboard. The default output will use the current console foreground color. But you can specify any other color that you would use with Write-Host. I even added some alias properties so you can use -foregroundcolor or -fg.

With this function I can see the result and have it dumped to the clipboard. Because the default text color is the same as my session, I don’t see any difference when using Out-Tee.

PS C:\> ps | where {$_.ws -gt 100mb} | out-tee

Or if I want to pretty it up, I can add a little color.

PS C:\> ps | where {$_.ws -gt 100mb} | out-tee -TextColor green

Of course, the clipboard is just text. But now I have something easier to use to save output to the clipboard so I can paste it into my documents, assuming I like the output I see on the screen. The one caveat is that this function only works with successful commands. Errors, warnings, or verbose statements won’t get dumped to the clipboard. I can think of some ways around that which I might try in a future version. But for my immediate needs this works just fine.

Download Out-Tee and give it a try.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

Posted in Friday Fun, PowerShell v2.0 | Tagged , , , , | Leave a comment