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 , , | 1 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

Convert Text to Object Updated

I’ve had a few comments and emails lately about my post and script on converting text to objects. I decided the function needed a little more lovin’ so today I have an updated version, complete with comment based help.

Function Convert-TextToObject {

<#
.Synopsis
Convert text to a PowerShell object
.Description
This function takes a collection of simple delimited lines and
turns them into an object. The function assumes a single delimiter.
The default delimiter is the colon (:). The item to the left will be
the property and the item on the left will be the property value.

By default the function will split on every delimiter, but you can use
the -SplitCount parameter to control the number of strings. If you don't
want to split at all use a value of 1. To split on the first delimiter
only, use a value of 2.

For example, if the string is: Foobar:  01/11/2012 09:41:28 and you tried
to split on the ":",  you would end up with 4 elements in the array. But
using a -SplitCount value of 2 would give you this:

Foobar
01/11/2012 09:41:28

Use $GroupCount to keep track of items that come in groups and write a new
object when the count has been reached. For example, this allows you to
pipe in a long collection of strings and turn every 5 into an object.

This function works best with command line tools that write list-like
output.

.Parameter Text
The text to be converted
.Parameter Delimiter
The text delimiter to split on. The colon (":") is the default.
.Parameter SplitCount
The number of strings to create when splitting. The default is to split
on all delimiters. If you don't want to split at all use a value of 1.
To split on the first delimiter only, use a value of 2.
.Parameter GroupCount
The number of piped in strings to group together to form a new object.
.Example
PS C:> tasklist /s server01 /fo list | where {$_} | convert-texttoobject -group 5

Take the output for Tasklist.exe as a list, strip out blank lines and pipe
to Convert-TextToObject. Turn every 5 items into an object.
.Example
PS C:\> get-content c:\work\data.txt | select -skip 4 | where {$_} | cto -group 4 -SplitCount 2 | format-list


LinkDate    : 11/20/2010 5:44:56 AM
DisplayName : 1394 OHCI Compliant Host Controller
DriverType  : Kernel
ModuleName  : 1394ohci

LinkDate    : 11/20/2010 4:19:16 AM
DisplayName : Microsoft ACPI Driver
DriverType  : Kernel
ModuleName  : ACPI

LinkDate    : 11/20/2010 4:30:42 AM
DisplayName : ACPI Power Meter Driver
DriverType  : Kernel
ModuleName  : AcpiPmi


Get the Data.txt file, skipping the first 4 lines and stripping out blanks.
Then create objects for every 4 lines. The SplitCount is set to 2 so that
the LinkDate value is the complete datetime stamp.
.Example
PS C:\> whoami /user /fo list | where {$_ -match ":"} | convert-texttoobject | format-table -auto

UserName      SID
--------      ---
serenity\jeff S-1-5-21-2858895768-3673612314-3109562570-1000
.Example
PS C:\> whoami /groups /fo list | where {$_ -match ":"} | cto -group 4 | format-list


Attributes : Mandatory group, Enabled by default, Enabled group
Type       : Well-known group
GroupName  : Everyone
SID        : S-1-1-0

Attributes : Mandatory group, Enabled by default, Enabled group, Group owner
Type       : Alias
GroupName  : BUILTIN\Administrators
SID        : S-1-5-32-544

...

Get group listing from Whoami.exe and filter out lines that don't have a colon.
Create objects for every 4 lines. This example is using the cto alias for the
Convert-TexttoObject function.
.Link
about_Split
New-Object

.Inputs
Strings

.Outputs
Custom object
#>


[cmdletbinding(SupportsShouldProcess=$True)]

param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a string to be parsed into an object",
ValueFromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$Text,
[Parameter(Position=1)]
[ValidateNotNullOrEmpty()]
[string]$Delimiter=":",
[ValidateScript({$_ -ge 0})]
[int]$SplitCount=0,
[int]$GroupCount
)

Begin {
    Write-Verbose "Starting $($myinvocation.mycommand)"
    #define a hashtable
    $myHash=@{}
    if ($GroupCount) {
        Write-Verbose "Grouping every $GroupCount items as an object"
    }
    #start an internal counter
    $i=0
    Write-Verbose "Skipping $Skip lines"
    Write-Verbose "Using $Delimiter delimiter"
    Write-Verbose "Splitting into $SplitCount lines. 0 means all."
}

Process {      
    Foreach ($item in $text) {
      if ($i -lt $GroupCount) {
            $i++
         }
         else {
            #reset
            $i=1
         }
       
         #split each line at the delimiter
         $data=$item -Split $delimiter,$SplitCount
         #remove spaces from "property" name
         $prop=$data[0].Replace(" ","")
         
         #trim
         $prop=$prop.Trim()
         $val=$data[1].Trim()
         
         #add to hash table
         Write-Verbose "Adding $prop to hash table with a value of $val"
         $myHash.Add($prop,$val)
         
         #if internal counter is equal to the group count
         #write the object and reset the hash table
         if ($i -eq $groupCount) {
            New-Object -TypeName PSObject -Property $myHash
            $myHash.Clear()
         }
    } #foreach
}
End {
    #create new object from hash table
    if ($myHash.count -gt 0) {
        New-Object -TypeName PSObject -Property $myHash
        Write-Verbose "Ending $($myinvocation.mycommand)"
    }
}

} #end function

Check out the original article to understand the basics. The major change here is the SplitCount parameter. Often you might end up with a line of text like this:

LinkDate : 11/20/2010 4:30:42 AM

The function needs to split the string into an array on the colon. But when you do that, the time stamp will “break”. The answer is to tell the Split operator how many strings to create. The default of 0 will split into all strings.

PS C:\> "DisplayName : ACPI Power Meter Driver" -split ":",0
DisplayName
 ACPI Power Meter Driver

If I try that with the time stamp string you see the problem.

PS C:\> "LinkDate    : 11/20/2010 4:30:42 AM" -split ":",0
LinkDate
 11/20/2010 4
30
42 AM

But now I can specify the number of strings and I get what I need.

PS C:\> "LinkDate    : 11/20/2010 4:30:42 AM" -split ":",2
LinkDate
 11/20/2010 4:30:42 AM

The new version of the function incorporates this. I also received a comment about including a skip feature. I kicked that idea around for awhile, but decided in the end that PowerShell already had a mechanism for skipping lines. I wanted to keep my function limited to a single purpose. So if your text output includes a number of lines you want to skip, before you begin converting text to object, use something like this:

PS C:\> get-content c:\work\data.txt | select -skip 4 | where {$_} | cto -group 4 -SplitCount 2 | format-list


LinkDate    : 11/20/2010 5:44:56 AM
DisplayName : 1394 OHCI Compliant Host Controller
DriverType  : Kernel
ModuleName  : 1394ohci

LinkDate    : 11/20/2010 4:19:16 AM
DisplayName : Microsoft ACPI Driver
DriverType  : Kernel
ModuleName  : ACPI

LinkDate    : 11/20/2010 4:30:42 AM
DisplayName : ACPI Power Meter Driver
DriverType  : Kernel
ModuleName  : AcpiPmi

The script file with the function, will also create an alias of cto. I hope you’ll download Convert-TexttoObject.-v1.5 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 CommandLine, PowerShell, PowerShell v2.0, Scripting | Tagged , , , | 3 Comments