Cash Talks - BS Walks RSS 2.0
# Saturday, 01 January 2011

I had an interesting problem where I needed to protect text within Quoted Strings from a series of Regex search and replace operations.  Trying to write even simple regular expressions replace process that would exclude text in quotes was too painful for my understanding of Regex.  I’m sure there are gurus who can throw that out but I’m not one of them. 

While my job was more extensive than simply upper case, what if you wanted to change all characters in a string to upper case except for ones within double quotes?  For example:

The “quick brown fox” jumped over the “lazy dog”
would become
THE “quick brown fox” JUMPED OVER THE “lazy dog”

There are number of quoted string regex expressions out there and I needed one that provided for escaped “ within the string that had a preceding  \.

The following regex replace would replace quoted strings with the word STUFF

[regex]::Replace($value,'"([^"\\]*(\\.[^"\\]*)*)"', 'STUFF')

Not much help but that’s where the regex MatchEvaluator comes in to play.  It’s a function that gets called for each match and the text it returns is what gets used.  For example, what if I wanted to upper case only characters within double quotes?  The following code would make quick work of it.

$value = 'The "quick brown fox" jumped over the "lazy dog"'

 

$QuotedTextMatchEvaluator = [System.Text.RegularExpressions.MatchEvaluator]{

       $args[0].ToString().ToUpper()

}

 

[regex]::Replace($value,'"([^"\\]*(\\.[^"\\]*)*)"',$QuotedTextMatchEvaluator)

This produces
The “QUICK BROWN FOX” jumped over the “LAZY DOG”

Simple enough but as I said it was the opposite of what I needed and I was doing more interesting work than generating uppercase.  My idea was to grab all of the quoted strings, save them in an array and then restore them when I was done.  The save-quotedstrings function replaces each quoted string with a unique token then restore-quotedstrings replaces the token with the saved string.  While context will dictate what is possible as a token, I chose to use an ASCII 1, a rarely used non-visible character followed by an incrementing number for each quoted string.  save-quotestrings returns the tokenized string and the array of saved quoted strings.  restore-quotedstrings takes a tokenized string and the saved string array and restores them.

 

function save-quotedstrings([string]$value)

{

       $SavedStrings = @()

       $QuotedTextMatchEvaluator = [System.Text.RegularExpressions.MatchEvaluator]{

              $SavedStrings += $args[0].ToString()

              [char]1 + ($SavedStrings.Count)

       }

       [regex]::Replace($value,'"([^"\\]*(\\.[^"\\]*)*)"',$QuotedTextMatchEvaluator),$SavedStrings

}

 

function restore-quotedstrings([string]$value,[array]$savedStrings)

{

       (1..$savedStrings.Count) | %{$value = $value.Replace([char]1 + "$_",$savedStrings[$_-1])}

       $value

}

 

 

$value = 'This "is \"a\" test" of a "quoted string" saver'

$value

$value,$savedStrings = save-quotedstrings $value

$value

$value = $value.ToUpper()

$value

$value = restore-quotedstrings $value $savedStrings

$value

The preceding code generates the following output.

This "is \"a\" test" of a "quoted string" saver
This _1 of a _2 saver
THIS _1 OF A _2 SAVER
THIS "is \"a\" test" OF A "quoted string" SAVER

Note:  the _ is show instead of the non-displayable ASCII 1.

Saturday, 01 January 2011 00:16:06 (GMT Standard Time, UTC+00:00)  #    Comments [55] -
Powershell
# Monday, 10 March 2008

In my previous blog entry, Easily setting Tags on Media Files using PowerShell, I described how to set the media tags used by common MP3 players.

The reason I had this need stemed from a frustration from RIPing Audio Books into MP3s.  For example, let's say I'm compressing a 10 CD book.  Even with "good" audio naming from Internet Naming sources, you get 10 different albums with numberd tracks from 1 to whatever.  Sometimes the naming fixes this and sometimes it does a miserable job.  Copying and renaming the files doesn't do the job.  When you RIP the CD the encoder creates Meta Tags Used by MP3 players.

When your favorite tool is a hammer...

I didn't even look for a tool to solve this problem so if there is one... that's not really the point.  I was curious if I could solve it very easily using Powershell.  This is what I want:

  • Copy all files to a single directory
  • Name all files with some base name with a Zero padded count.
  • Provide for a flexible means for setting Tags on all Files.  For example, Author
  • Set the Album Tag so all files will appear as a single album
  • Set Meta Track number to coorespond the the Count and Title to the Filename

When I RIP the CDs it easy to get them all under a single directory and organize the directories Alphabetically so the CDs are in order.  The individually named where they also sort alphabetically.  As such the following line will return all MP3s under a directory in the order they should be played:

dir -Recurse -Filter *.mp3

As such, I need a CmdLet where I can pipe these.  This CmdLet will also take an Array parameter if that is your preference.

function Copy-NumberFile
 
( [
system.IO.FileInfo] $file=$(Throw "File is required"
)
 
, [string] $BaseName=$(Throw "BaseName is required"
)
  ,
$DestDir=$(Throw "DestDir is required"
)
  , [
int] $Count=$(Throw "Count is required"
)
  , [
int] $Digits=
1
  , [
switch]
$Verbose
 
, [switch]
$PassThru
 
)
{
 
$nbrStr = "{0:d$Digits}" -f
$Count
 
$NewName =
"$($BaseName)$($nbrStr)$($file.Extension)"
 
$NewFileName = [System.IO.Path]::Combine($DestDir.FullName,$NewName
)
 
if ($Verbose
)
  {
   
Write-Host
"Copy $($file.fullname) to $NewFileName"
 
}
  
Copy-Item $file.FullName -Destination $NewFileName -PassThru:
$PassThru
}

Function
Organize-AudioBookFiles 
 
( [
System.IO.DirectoryInfo]
$DestDir
 
, [hashtable]
$Tags
 
, [string]
$BaseName
 
, [int]$CountStart=
1
  ,
$Digits=-
1
  , [
array]$Files
)
{
 
begin
 
{
    [
array] $fileList =
$null
   
function AddFile( $FileToCheck
)
    {
     
if ($fileToCheck -ne $null
)
      {
        [
system.IO.FileInfo] $file =
$FileToCheck
       
if (!$file.Exists) { Throw "File does Not Exist"
}
       
$file
     
}
    }
    
    if (!$DestDir.Exists
)
    {
      $DestDir.Create
()
    }
    
if ((dir $DestDir.FullName | Measure-Object).Count -gt
0)
    {
     
Throw
"Destination Directory $($DestDir.FullName) is not empty"
    
}
   
foreach ($file in $files
)
    {
     
$fileList += AddFile($file

    }
  }
 
 
process
 
{
   
$fileList += AddFile($_
)
  }

  end
 
{
    $Count =
$CountStart
    
if ($digits -lt
0)
    {
      [
int]$Digits = [System.Math]::Truncate(([System.Math]::Log10($fileList.Count + $Count)))+
1
    }
   
foreach ($file in $fileList
)
    {
     
$file = Copy-NumberFile -file $file -DestDir $DestDir -BaseName $BaseName
`
       
-Digits $digits -Count ($Count) -Verbose
-PassThru
     
$filename = $file.
Name
     
$Tags.Track =
$Count
     
$Tags.Title = $filename.Substring(0,($filename.length-$file.extension.length
))
     
SetMedia-Tags -file $file -Tags
$Tags
     
$Count
++
   
}
  }
}

I have a helper function "Copy-NumberFile".  While I'm not sure if I'll ever need it in another operation, I can imagine it.  It copies a file to a Destination Directory, renames it with a BaseName followed by a Number and preserving the extension.  The number of Zero padded digits is specified in the $Digits parameter.  The $PassThru allows the caller to get the FileInfo for the created file.

An example usage might look like:

$Tags = @{Artists="Book Author";Album="Book Title";Comment="This is a comment";Genres=@("Fiction")}
$files = dir -Recurse -Filter *.mp3
Organize-AudioBookFiles -Files $files -DestDir "C:\temp\Book" -BaseName "BookName " -Tags $Tags

Monday, 10 March 2008 02:27:18 (GMT Standard Time, UTC+00:00)  #    Comments [42] -
Powershell

This weekend I found the need to set the Media Meta Tags in MP3 files I have RIPed.  I found this Huddled Masses article for using the taglib-sharp library.  I find Hashtables as a convenient machine for passing Pthese kinds of adhoc Name\Value pairs.

[void] [Reflection.Assembly]::LoadFile(".\taglib-sharp.dll")

function SetMedia-Tags
 
( [System.IO.FileInfo] $File=$(Throw "File is required")
  , [
hashtable] $Tags=$(Throw "Tags are required"
))
{
  $media = [TagLib.File]::Create($file.FullName
)
  foreach ($tag in $Tags.keys
)
  {
    $media.Tag.$tag = $Tags[$tag
]
  }
  $media.Save
()
}

Example Usage:

$Tags = @{Artists="Max Headgroom";Album="Smoke";Comment="This is a comment";Genres=@("Crazy")}
SetMedia-Tags -File ".\file1.mp3" -Tags $Tags

I used this code in a more specific project for organizing Audio Book MP3s I'll describe in a subsequent post.

Monday, 10 March 2008 01:12:26 (GMT Standard Time, UTC+00:00)  #    Comments [36] -
Powershell
# Friday, 07 March 2008

One of the interesting things about having a blog is seeing what people are searching for when they navigate to an entry.  On at least a dozen occasions I've seen searches where people were obviously trying to invoke NAnt from PowerShell.  Because I've talked about both NAnt and PowerShell without speaking to this subject, they have been disappointed in what they've found in my blogs.  Indeed, when I've performed my own searches, I've noticed there isn't a lot of useful info on doing this subject.  Especially at detecting an error in the NAnt script.

This isn't the most comprehensive script but it does the basics:

function Invoke-NAnt ($NantFile,$target,[hashtable]$Properties)
{
  $sb = New-Object
"System.Text.StringBuilder"
 
if ($properties -ne $null)
  {
    foreach ($key in $Properties.Keys)
    {
      [void] $sb.Append( '"' + "-D:$key=$($Properties.$key)" + '" ' )
    }
  }
  
  nant "-f:$NantFile" $target $sb.ToString()

 
if (-not $?)
  {
    Throw
"Nant Failed"
 
}
}

Example Usage:

Invoke-NAnt Test.build "TestTarget" @{Test1="Value1";Test2="Value2"}

Test.Build contains:

<project>
  <
target name="TestTarget"
>
    <
echo message="Test1=${Test1}"
/>
    <
echo message="Test2=${Test2}"
/>
    <
fail message="This is a failure"
/>
  </
target
>
</
project>

Output looks like:

NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/Projects/PSNant/Test.build
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: TestTarget

TestTarget:

     [echo] Test1=Value1
     [echo] Test2=Value2

BUILD FAILED

C:\Projects\PSNant\Test.build(6,6):
This is a failure

Total time: 0 seconds.

Nant Failed
At C:\Projects\PSNant\InvokeNant.PS1:19 char:10
+     Throw  <<<< "Nant Failed"

This particular script is designed to fail with the 'fail task'.  This is because I was having trouble detecting errors.

  if (-not $?)
  {
    Throw
"Nant Failed"
 
}

The $? variable is set to $true if the previous instruction succeeded.  As of yet, I'm not getting the actual failure message.  If someone can help with that, I'd appreciate it.

Other usage comments:

The $properties parameter takes a hash table that gets marshaled into -D:<propname>=<propvalue> line arguments. I find the syntax of @{Test1="Value1";Test2="Value2"} to be more natural for PowerShell.

This function will return an array of text lines which are the output from NAnt.  If you don't want them to pipe out you'll need to assign it to a variable or [void] it.

$NantOutput = Invoke-NAnt Test.build "TestTarget" @{Test1="Value1";Test2="Value2"}

Finally:  Obviously this needs to be dressed out a little more for supporting some useful parameters to NAnt like Logger and Default Framework. 

Friday, 07 March 2008 23:26:48 (GMT Standard Time, UTC+00:00)  #    Comments [1] -
Powershell
# Saturday, 23 February 2008

I'm in the initial stages of attempting to control Linux servers from Windows using PowerShell.

The obvious path is using SSH.  The question is how.  Unfortunately with all the buzz around PowerShell Remoting, what I'm finding is a lot of the reverse... accessing PowerShell through SSH.  The reciprocity of this may make the reverse true but I haven't found the confirmation link yet.

The long way around the block might be through Cygwin and Putty.  The link is a little old but it shows a way.  I'm hoping for a direct path from PowerShell so I can minimize installation dependencies.

Update:

NetCmdlets is the "almost" direct path.  It looks good but too pricy for the way I want use it.  Besides, it's "just" a wrapper for rsh.exe.  Looks like I'll be attempting my own wrapper.  Anyone else try to do this?

Saturday, 23 February 2008 05:11:47 (GMT Standard Time, UTC+00:00)  #    Comments [49] -
Powershell | Virtual Linux
# Monday, 18 February 2008

The other day I was given a hard drive and asked to analyze what was on it.  While it was easy to determine it had over 300,000 files and about 10 Gigs I didn’t want to navigate through the whole thing looking in the hundreds of folders.  I figured a good thing to know was the number of files and size by extension.

 

Powershell to the rescue.  After several iterations of various techniques I came up with a CmdLet allowing me to measure any kind of object.  You can specify which property to use as the Group and for measurement.  I’m not sure what else I’ll use it for, but I love making things generic.

 

function MeasureGroup-Object

  ( [string]$group=$(Throw "Group Name is Required")

  , [string]$property=$(Throw "Property Name is Required")

  , $items

  )

{

  begin

  {

    function processItem($item)

    {

      $key = $item.$group

     

      if ($Aggregate.$key -eq $null)

      {

        $Aggregate.$key = @{Count=0;Sum=0}

      }

      $Aggregate.$key.Count += 1

      $Aggregate.$key.Sum += $item.$property

    }

 

    # Hash table to collect stats

    $Aggregate = @{}

 

    if ($items -ne $null)

    {

      foreach ($item in $items)

      {

        processItem $item

      }

    }

  }

  process

  {

    if ($_ -ne $null)

    {

      processItem $_

    }

  }

  end

  {

    function AddProperty ($object,$name,$value)

    {

      $member = new-object management.automation.PSNoteProperty $name,$value

      $object.psobject.members.Add($member)

    }

 

    foreach ($key  in $Aggregate.Keys)

    {

      $obj = new-object management.automation.psobject

      AddProperty $obj $group $key

      AddProperty $obj Count $Aggregate.$key.Count

      AddProperty $obj Sum $Aggregate.$key.Sum

      $obj

    }

  }

}

 

 

So let’s put it to work.  First you have to collect the objects you want to measure.

 

$files = Get-ChildItem -Recurse | where {$_ -is [System.IO.FileInfo]}

 

This will recursively collect all the files under the current directory.  It helps to filter out the Directories.  Because they don’t have Extensions, they would end up inflating the stats for files without extensions.

 

Now to use the MeasureGroup-Object.

 

$stats = MeasureGroup-Object Extension Length $files

 

The $stats variable will contain an array with an entry for each extension type.  Each entry will have an Extension, Count and Sum value.

 

The following will display the results but not in any particular order.

 

$stats

 

This will be more interesting.

 

# Get Top 5 file types by count

$stats | Sort-Object Count -Descending | Select-Object -First 5

 

Extension                Count                     Sum

---------                -----                     ---

.cs                        842                 3018481

.hxs                       765               980371275

.cab                       756              1801724080

.sql                       538                 6734980

.dll                       367                79635976

 

These are also interesting views.

 

# Get Top 5 file types by total Length

$stats | Sort-Object Sum -Descending | Select-Object -First 5

 

# Display all Extensions

$stats | Sort-Object Extension

 

Some comments about the implementation.

 

The End processing loops through the $Aggregate results and builds PSObjects with Note values.  The PSObjects play nice with the Sort, Select and default display.

 

Related Post: PowerShell Directory Size

Monday, 18 February 2008 00:57:25 (GMT Standard Time, UTC+00:00)  #    Comments [712] -
Powershell
# Sunday, 17 February 2008

This is a useful template for starting a CmdLet that needs to work in a PipeLine or by passing parameters.

 

Processing is implemented in the processItem sub-function.  All the “–ne $null” conditions handle the differences between pipeline and parameter invocation.

 

function Test-PipelineOrParm ($parm)

{

  begin

  {

    function processItem($item)

    {

      # Implement processing in this function

      "processItem $item"

    }

   

    if ($parm -ne $null)

    {

      foreach ($item in $parm)

      {

        "begin $(processItem $item)"

      }

    }

  }

  process

  {

    if ($_ -ne $null)

    {

      "process $(processItem $_)"

    }

  }

  end

  {

    Write-Host "Done"

  }

}

 

These two examples demonstrate calling the template.

 

Write-Host "Test Pipeline processing"

Get-ChildItem | Test-PipelineOrParm

 

Write-Host "Test Parm processing"

Test-PipelineOrParm $(Get-ChildItem)

 

Sunday, 17 February 2008 23:26:38 (GMT Standard Time, UTC+00:00)  #    Comments [76] -
Powershell

The past few weeks I’ve been working on a Powershell OO scripting framework.  I figure I better go ahead and publish it because I could keep tinkering on it for a long time.  I suspect it’s not done and even has a bug or two but it’s in decent shape.  While this is not “real” Object Oriented it does support:

 

·         Polymorphism

·         Encapsulation

·         Constructors with parameters

·         Notes – read-write variables

·         Methods – scriptblocks

·         Properties with Get scriptblocks and optional Set scriptblocks

·         Static and Private Notes and Methods

 

The New-PSClass cmdlet takes a script which acts as Class Definition.  Keywords note, method, property and constructor are local functions in New-PSClass and act as Class Definition by attaching objects to a PSClass Object returned by the CmdLet.

 

The PSClass has a New() method.  Parameters to New are passed to the constructor script.  Due to a bug in V1.0 of PowerShell, the Param statement does not work in the scriptblocks in PSClass.  While I have not tried it yet, I have good reason to believe that it will work in V2.0.  While it’s not as nice, the $Args variable works fine.

 

Instead of trying to describe all the syntax as small points in the blog entry, I’ll present an example Animal Class with a Dog and Bird Classes that inherit from it.  While looking through it take note of:

·         Private Notes use a –private switch.  Within the class, scripts access them through a $private variable.  Encapsulatioen isolates them from inherited classes and easy public access.

·         Static Notes and Methods are attached to the PSClass object.  This example shows a Static Note accessed in the Constructors.  This may seem non-intuitive considering the Class Object used in the Constructor is not created until New-PSClass is completed.   This is because the constructor script is not executed until an action Object is created using the New() method.

·         While not demonstrated, private methods are supported and attached to the $private variable available in script execution.

·         Note the use the –override switch on the ToString() method on the Dog Class.  It can call methods on the base class through Invoke-BaseClassMethod

·         Base function in constructors to call inherited constructors

 

 

$AnimalClass = New-PSClass Animal {

 

  note -static ObjectCount 0

  method -static DisplayObjectCount {

    "$($this.ClassName) has $($this.ObjectCount) instances"

  }

 

  note -private Name

  note -private Legs

 

  constructor {

    $private.Name = $Args[0]

    $private.Legs = $Args[1]

   

    $AnimalClass.ObjectCount += 1

  }

 

  property Name {

    $private.Name

  } -set {

    Write-Host "Renaming $($this.Class.ClassName) '$($private.Name)' to '$($Args[0])'"

    $private.Name = $Args[0]

  }

 

  property Legs {

    $private.Legs

  }

 

  method -override ToString {

    "A $($this.Class.ClassName) named $($this.name) with $($this.Legs) Legs"

  }

 

  method Speak {

    Throw "not implemented"

  }

}

 

$DogClass = New-PSClass -inherit $AnimalClass Dog {

  note -static ObjectCount 0

  method -static DisplayObjectCount {

    "$($this.ClassName) has $($this.ObjectCount) instances"

  }

 

  constructor {

    Base $Args[0] 4  

 

    $DogClass.ObjectCount += 1

  }

 

  method -override ToString  {

    "$(Invoke-BaseClassMethod 'ToString') with extra"

  }

 

  method -override Speak {

    "Arf"

  }

}

 

$BirdClass = New-PSClass -inherit $AnimalClass Bird {

  note -static ObjectCount 0

  method -static DisplayObjectCount {

    "$($this.ClassName) has $($this.ObjectCount) instances"

  }

 

  constructor {

    Base $Args[0] 2 

 

    $BirdClass.ObjectCount += 1

  }

 

  method -override Speak {

    "Squawk"

  }

}

 

$Dog = $DogClass.New("Bowser")

$Dog.ToString()

$Dog.Name = "Duke"

$Dog.ToString()

$Dog.Speak()

 

$Bird = $BirdClass.New("Tweedy")

$Bird.ToString()

$Bird.Speak()

 

$DogClass.DisplayObjectCount()

$BirdClass.DisplayObjectCount()

$AnimalClass.DisplayObjectCount()

 

Output:

A Dog named Bowser with 4 Legs with extra

Renaming Dog 'Bowser' to 'Duke'

A Dog named Duke with 4 Legs with extra

Arf

A Bird named Tweedy with 2 Legs

Squawk

Dog has 1 instances

Bird has 1 instances

Animal has 2 instances

 

The cool thing about the implementation is it’s all in PowerShell.  It’s a big chunk of code and I’m sure it’s not its final version, but it has a lot of cool techniques and some that should be fixed up.  Also, I’ve been working on an XMS Serialization, Attribute support, and the ability to attach PSClass objects to any PowerShell object.

 

These PowerShell Scripts are in the attached zip file.

 

# ===================================================================================

# New-PSClass

#     Object Oriented Scripting in Powershell

#     http://www.cashfoley.com/

# ===================================================================================

# Inspired by Bruce Payette's "Windows PowerShell in Action"

# Chapter 8 Script to add a CustomClass "keyword" to PowerShell

# http://manning.com/payette/

# ===================================================================================

 

function New-PSClass

  ( [string] $ClassName = { Throw "ClassName required for New-PSClass"}

  , [scriptblock] $definition = { Throw "Definition required for New-PSClass"}

  , $Inherit

  )

 

{

  #======================================================================

  # These Subfunctions are used in Class Definition Scripts

  #======================================================================

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  # Subfunction: constructor

  #   Assigns Constructor script to Class

  # - - - - - - - - - - - - - - - - - - - - - - - -

  function constructor ( [scriptblock] $script=$(Throw "Script is required for 'constructor'"))

  {

    if ($class.ConstructorScript)

    {

      Throw "Only one Constructor is allowed"

    }

    $class.ConstructorScript = $script

  }

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  # Subfunction: note

  #   Adds Notes record to class if non-static

  # - - - - - - - - - - - - - - - - - - - - - - - -

  function note

  {

    param ( [string]$name={Throw "Note Name is Required"}

          , $value=""

          , [switch] $private

          , [switch] $static

          )

   

    if ($static)

    {

      if ($private)

      {

          Throw "Private Static Notes are not supported"

      }

      Attach-PSNote $class $name $value

    }

    else

    {

      $class.Notes += @{Name=$name;DefaultValue=$value;Private=$private}

    }

  }

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  # Subfunction: method

  #   Add a method script to Class definition or

  #   attaches it to the Class if it is static

  # - - - - - - - - - - - - - - - - - - - - - - - -

  function method ([string]$name=$(Throw "Name is required for 'method'"), [scriptblock] $script=$(Throw "Script is required for 'method'"), [switch] $static, [switch]$private, [switch]$override )

  {

    if ($static)

    {

      if ($private)

      {

          Throw "Private Static Methods not supported"

      }

      Attach-PSScriptMethod $class $name $script

    }

    else

    {

      $class.Methods[$name] = @{Name=$name;Script=$script;Private=$private;Override=$override}

    }

  }

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  # Subfunction: property

  #   Add a property to Class definition or

  #   attaches it to the Class if it is static

  # - - - - - - - - - - - - - - - - - - - - - - - -

  function property ([string]$name, [scriptblock] $get=$(Throw "Get Script is required for 'property'"), [scriptblock] $set, [switch] $static, [switch]$private, [switch]$override )

  {

    if ($static)

    {

      if ($private)

      {

          Throw "Private Static Properties not supported"

      }

      Attach-PSProperty $class $name $get $set

    }

    else

    {

      $class.Properties[$name] = @{Name=$name;GetScript=$get;SetScript=$set;Private=$private;Override=$override}

    }

  }

 

  $class = new-object Management.Automation.PSObject

 

  # Class Internals

  Attach-PSNote $class ClassName $ClassName

  Attach-PSNote $class Notes @()

  Attach-PSNote $class Methods @{}

  Attach-PSNote $class Properties @{}

  Attach-PSNote $class BaseClass $Inherit

  Attach-PSNote $class ConstructorScript

  Attach-PSNote $class PrivateName "__$($ClassName)_Private"

 

  Attach-PSScriptMethod $class New {

    $instance = new-object Management.Automation.PSObject

 

    $this.AttachObject( $instance

    $this.Initialize( $instance, $Args )

   

    $instance

  }

 

  Attach-PSScriptMethod $class Initialize {

    if ($this.ConstructorScript -ne $null)

    {

      __PSClass-Initialize $Args[0] $Args[1]

    }

  }

 

  Attach-PSScriptMethod $class AttachObject {

    __PSClass-AttachObject $Args[0]

  }

 

  Attach-PSScriptMethod $class __LookupClassObject {

    __PSClass-LookupClassObject $Args[0] $Args[1]

  }

 

  Attach-PSScriptMethod $class InvokeMethod {

    __PSClass-InvokeMethod $Args[0] $Args[1] $Args[2]

  }

 

  Attach-PSScriptMethod $class InvokeProperty {

    __PSClass-InvokePropertyMethod $Args[0] $Args[1] $Args[2] $Args[3]

  }

 

  # At last, execute definition script

  &$definition

 

  # return constructed class

  $class

}

 

# ===================================================================================

# These helper Cmdlets should only be called by New-PSClass.  They exist to reduce

# the amount of code attached to each PSClass object.  They rely on context

# variables not passed as parameters.

# ===================================================================================

# __PSClass-Initialize

#    Invokes Constructor Script and provides helper Base function to facilitate

#    Inherited Constructors

# ===================================================================================

function __PSClass-Initialize ($instance, $params)

{

  function Base

  {

    if ($this.Class.BaseClass -eq $null)

    {

      Throw "No BaseClass implemented for $($this.Class.ClassName)"

    }

    $this.Class.BaseClass.Initialize($this,$Args)

  }

 

  trap {

    $errorMsg = $_.Exception.Message + $_.Exception.ErrorRecord.InvocationInfo.PositionMessage

    Throw $errorMsg

    $_.ErrorDetails = $_.Exception.ErrorRecord.InvocationInfo.PositionMessage

  }

 

  $constructor = $this.ConstructorScript

 

  $private = $Instance.($this.privateName)

  $this = $instance

 

  $constructor.InvokeReturnAsIs( $params )

}

 

# ===================================================================================

# __PSClass-AttachObject

#    Attaches Notes, Methods, and Properties to Instance Object

# ===================================================================================

function __PSClass-AttachObject ($instance)

{

  function AssurePrivate

  {

    if ($instance.($this.privateName) -eq $null)

    {

      Attach-PSNote $instance ($this.privateName) (new-object Management.Automation.PSObject)

      Attach-PSNote $instance.($this.privateName) __Parent $instance

    }

  }

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  #  Attach BaseClass

  # - - - - - - - - - - - - - - - - - - - - - - - -

  if ($this.BaseClass -ne $null)

  {

    $this.BaseClass.AttachObject($instance)

  }

 

  Attach-PSNote $instance Class $this

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  #  Attach Notes

  # - - - - - - - - - - - - - - - - - - - - - - - -

  foreach ($note in $this.Notes)

  {

    if ($note.private)

    {

      AssurePrivate

      Attach-PSNote $instance.($this.privateName) $note.Name $note.DefaultValue

    }

    else

    {

      Attach-PSNote $instance $note.Name $note.DefaultValue

    }

  }

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  #  Attach Methods

  # - - - - - - - - - - - - - - - - - - - - - - - -

  foreach ($key in $this.Methods.keys)

  {

    $method = $this.Methods[$key]

    $targetObject = $instance

 

    # Private Methods are attached to the Private Object.

    # However, when the script gets invoked, $this needs to be

    # pointing to the instance object. $ObjectString resolves

    # this for InvokeMethod

    if ($method.private)

    {

        AssurePrivate

        $targetObject = $instance.($this.privateName)

        $ObjectString = '$this.__Parent'

    }

    else

    {

        $targetObject = $instance

        $ObjectString = '$this'

    }

 

    # The actual script is not attached to the object.  The Script attached to Object calls

    # InvokeMethod on the Class.  It looks up the script and executes it

    $instanceScriptText = $ObjectString + '.Class.InvokeMethod( "' + $method.name + '", ' + $ObjectString + ', $Args )'

    $instanceScript = $ExecutionContext.InvokeCommand.NewScriptBlock( $instanceScriptText )

 

    Attach-PSScriptMethod $targetObject $method.Name $instanceScript  -override:$method.override

  }

 

  # - - - - - - - - - - - - - - - - - - - - - - - -

  #  Attach Properties

  # - - - - - - - - - - - - - - - - - - - - - - - -

  foreach ($key in $this.Properties.keys)

  {

    $Property = $this.Properties[$key]

    $targetObject = $instance

 

    # Private Properties are attached to the Private Object.

    # However, when the script gets invoked, $this needs to be

    # pointing to the instance object. $ObjectString resolves

    # this for InvokeMethod

    if ($Property.private)

    {

      AssurePrivate

      $targetObject = $instance.($this.privateName)

      $ObjectString = '$this.__Parent'

    }

    else

    {

      $targetObject = $instance

      $ObjectString = '$this'

    }

 

    # The actual script is not attached to the object.  The Script attached to Object calls

    # InvokeMethod on the Class.  It looks up the script and executes it

    $instanceScriptText = $ObjectString + '.Class.InvokeProperty( "GET", "' + $Property.name + '", ' + $ObjectString + ', $Args )'

    $getScript = $ExecutionContext.InvokeCommand.NewScriptBlock( $instanceScriptText )

 

    if ($Property.SetScript -ne $null)

    {

      $instanceScriptText = $ObjectString + '.Class.InvokeProperty( "SET", "' + $Property.name + '", ' + $ObjectString + ', $Args )'

      $setScript = $ExecutionContext.InvokeCommand.NewScriptBlock( $instanceScriptText )

    }

    else

    {

      $setScript = $null

    }

 

    Attach-PSProperty $targetObject $Property.Name $getScript $setScript -override:$Property.override

  }

}

 

# ===================================================================================

# __PSClass-LookupClassObject

#   intended to look up methods and property objects on the Class.  However,

#   it can be used to look up any Hash Table entry on the class.

#

#   if the object is not found on the instance class, it searches all Base Classes

#  

#   $ObjectType is the HashTable Member

#   $ObjectName is the HashTable Key

#

#   it returns the Class and Hashtable entry it was found in

# ===================================================================================

function __PSClass-LookupClassObject ($ObjectType, $ObjectName)

{

  $object = $this.$ObjectType[$ObjectName]

  if ($object -ne $null)

  {

    $this

    $object

  }

  else

  {

    if ($this.BaseClass -ne $null)

    {

      $this.BaseClass.__LookupClassObject($ObjectType, $ObjectName)

    }

  }

}

 

# ===================================================================================

# __PSClass-InvokeScript

#   Used to invoke Method and Property scripts

#     It adds an error handler so Script Info can be seen in the error

#     It marshals $this and $private variables for the context of the script

#     It provides a helper Invoke-BaseClassMethod for invoking base class methods

# ===================================================================================

function __PSClass-InvokeScript ($class, $script, $object, $parms )

{

  function Invoke-BaseClassMethod ($methodName, $parms)

  {

              if ($this.Class.BaseClass -eq $null)

              {

                     Throw "$($this.Class.ClassName) does not have a BaseClass"

              }

              $class,$method = $this.Class.BaseClass.__LookupClassObject('Methods', $MethodName)

   

    if ($method -eq $null)

    {

      Throw "Method $MethodName not defined for $className"

    }

    __PSClass-InvokeScript $class $method.Script $this $parms

  }

 

  trap {

      $errorMsg = $_.Exception.Message + $_.Exception.ErrorRecord.InvocationInfo.PositionMessage

      Throw $errorMsg

      $_.ErrorDetails = $_.Exception.ErrorRecord.InvocationInfo.PositionMessage

  }

 

  $this = $object

  $private = $this.($Class.privateName)

 

  $script.InvokeReturnAsIs( $parms )

}

 

# ===================================================================================

# __PSClass-InvokeMethod

#   Script called by methods attached to instances.  Looks up Method Script

#   in instance class or in inherited class

# ===================================================================================

function __PSClass-InvokeMethod($MethodName, $instance, $parms)

{

  $class,$method = $this.__LookupClassObject('Methods', $MethodName)

 

  if ($method -eq $null)

  {

      Throw "Method $MethodName not defined for $($this.ClassName)"

  }

 

  __PSClass-InvokeScript $class $method.Script $instance $parms

}

 

# ===================================================================================

# __PSClass-InvokePropertyMethod

#   Script called by property scripts attached to instances.  Looks up property Script

#   in instance class or in inherited class

# ===================================================================================

function __PSClass-InvokePropertyMethod ($PropertyType, $PropertyName, $instance, $parms)

{

  $class,$property = $this.__LookupClassObject('Properties', $PropertyName)

 

  if ($property -eq $null)

  {

    Throw "Property $PropertyName not defined for $($this.ClassName)"

  }

 

  if ($PropertyType -eq "GET")

  {

    __PSClass-InvokeScript $class $property.GetScript $instance $parms

  }

  else

  {

    __PSClass-InvokeScript $class $property.SetScript $instance $parms

  }

}

 

# ===================================================================================

function Attach-PSNote

{

  param ( [PSObject]$object=$(Throw "Object is required")

        , [string]$name=$(Throw "Note Name is Required")

        , $value

        )

 

  if (! $object.psobject.members[$name])

  {

    $member = new-object management.automation.PSNoteProperty `

          $name,$value

   

    $object.psobject.members.Add($member)

  }

  else

  {

    if($value)

    {

      $object.$name = $value

    }

  }

}

 

# ===================================================================================

function Attach-PSScriptMethod

{

  param ( [PSObject]$object=$(Throw "Object is required")

        , [string]$name=$(Throw "Method Name is Required")

        , [scriptblock] $script

        , [switch] $override

        )

 

  $member = new-object management.automation.PSScriptMethod `

      $name,$script

 

  if ($object.psobject.members[$name] -ne $null)

  {

    if ($override)

    {

      $object.psobject.members.Remove($name)

    }

    else

    {

      Throw "Method '$name' already exists with out 'override'"

    }

  }

 

  $object.psobject.members.Add($member)

}

 

# ===================================================================================

function Attach-PSProperty

{

  param ( [PSObject]$object=$(Throw "Object is required")

        , [string]$name=$(Throw "Method Name is Required")

        , [scriptblock] $get=$(Throw "get script is required")

        , [scriptblock] $set

        , [switch] $override

        )

  if ($set)

  {

    $scriptProperty = new-object management.automation.PsScriptProperty `

      $name,$get,$set

  }   

  else

  {

    $scriptProperty = new-object management.automation.PsScriptProperty `

      $name,$get

  }   

 

  if ( $object.psobject.properties[$name] -and $override)

  {

    $object.psobject.properties.Remove($name)

  }

 

  $object.psobject.properties.add($scriptProperty)

}

PSClass.zip (4.24 KB)
Sunday, 17 February 2008 20:19:52 (GMT Standard Time, UTC+00:00)  #    Comments [0] -
Powershell
# Wednesday, 16 January 2008

Today I went in search of a PowerShell script to calculate the size of a directory.  I was amazed to find very little.  Even worse, I was disappointed in what I found.  Here’s an example:

 

http://iformattable.blogspot.com/2007/07/powershell-script-to-determine.html

 

Cleaned up a little it looks like:

 

function Get-DirectorySize

{

      param ($path = {(Get-Location).Path})

     

      $size = Get-ChildItem $path -recurse -force `

            | ? { $_.GetType() -like 'System.IO.DirectoryInfo'} `

            | % {$_.GetFiles() } `

            | Measure-Object -Property Length -Sum `

            | Measure-Object -Property Sum -Sum 

     

      $size2 = (Get-item $path).GetFiles() `

            | Measure-Object -Property Length -Sum 

     

      [System.Math]::Round(($size.Sum + $size2.Sum) /  (1024 * 1024)).ToString() + "MB"

}

 

Returning a String is ridiculous.  If you are going to all the trouble of using Measure-Object you might as well get a rich output.

 

function Get-DirectorySize

{

      param ($path = {(Get-Location).Path})

     

      Get-ChildItem $path -recurse -force `

            | ? { $_.GetType() -like 'System.IO.FileInfo'} `

            | Measure-Object -Property Length -Sum -Average -Minimum -Maximum`

}

 

This is cleaner and has more interesting output providing an aggregated object with Sum, Average, Minimum and Maximum properties.  However, the biggest problem is that is extremely SLOW.  Try to use this on a directory with thousands of small files.  Even worse, try to use it on a network path with thousands of small files!

 

This approach tries to leverage all the cool PowerShell piping and CmdLets.  I prefer these to the old school VBScript and COM object approaches.  However, sometimes old school beats new school. 

 

function Get-DirectorySize

{

      param ($path = {(Get-Location).Path})

     

      if (! $GLOBAL:_FileSystemObject)

      {

            $GLOBAL:_FileSystemObject = New-Object -comobject "Scripting.FileSystemObject"

      }

      ($GLOBAL:_FileSystemObject.GetFolder($path)).Size

}

 

The old FileSystemObject is hard to beat for this kind of job.  In this example, I assure I don’t have to make more than one FileSystemObject. 

 

function Get-DirectorySize

{

      begin

      {

            if (! $GLOBAL:_FileSystemObject)

            {

                  $GLOBAL:_FileSystemObject = New-Object -comobject "Scripting.FileSystemObject"

            }

            $accumulation = 0

      }

      process

      {

            if ($_ -eq $null)

            {

                  $_ = (Get-Location).Path

            }

           

            if ($_ -is [System.IO.DirectoryInfo])

            {

                  $_ = $_.FullName

            }

           

            $accumulation += ($GLOBAL:_FileSystemObject.GetFolder($_)).Size

      }

      end

      {

            $accumulation

      }

}

 

This final example is Pipeline friendly and marshals a DirectoryInfo parameter.  This function is very flexible and should be as fast as you are going to get. 

 

NOTE:  $accumulation accumulates multiple directories in the pipeline, not individual file sizes.

 

Related Post: Analyze Hard Drive Extensions with PowerShell

Wednesday, 16 January 2008 14:17:35 (GMT Standard Time, UTC+00:00)  #    Comments [1602] -
Powershell
# Wednesday, 12 December 2007

I haven’t posted anything in a while but it’s not because I’ve been idle.  I’ve been doing a massive amount of PowerShell and will be blogging some real cools stuff in the coming weeks.

 

For now, I’m offering a little tidbit I wrote yesterday for reading a file into a binary byte array.  It was necessary for Publishing RDL files in Sequel Server Reporting Services – more on that later too.

 

function read-filebytes($fileItem={Throw "fileItem is required for read-filebytes"})

{

  try {

    [system.io.stream]$stream = [system.io.File]::OpenRead($fileItem.fullname)

    try {

      [byte[]]$filebytes = New-Object byte[] $stream.length

      [void] $stream.Read($filebytes, 0, $stream.Length);

      $filebytes

    } -finally {

      $stream.Close();

    }

  } -catch {

    Write-Error "Error reading file $fileItem - $_"

    return

  }

}

 

I’m using the TryCatchFinally Cmdlet it’s really made error management much easier.

 

The real trick to this script is:

 

  [byte[]]$filebytes = New-Object byte[] $stream.length

 

This pre-allocates the memory for the Stream Read method.  Not a common technique in PowerShell.

 

Cash

Wednesday, 12 December 2007 23:19:29 (GMT Standard Time, UTC+00:00)  #    Comments [38] -
Powershell
# Saturday, 13 October 2007

Omar Al Zabir wrote a Code Project article entitled Deployment made simple using Powershell where I added the following comment. 

I've been using and extending NAnt for 4 years. It has some really nice things and I've done some amazing things with it. However, my current strategy is to leave it. - It's just converting all my existing stuff that will make it painful and slow.

We plan to use MSBuild for Build Control and PowerShell for Deployment. Currently we use NAnt for both of these things.

The worst thing about NAnt and MSBuild is it's XML. The semantics of the language are limited by XML. XSLT is powerful but painful for the same reason. Neither language supports IF, THEN, ELSE because of the constraints of XML. The most glaring problem with these languages is the lack of Parametrized control. A "Target" has expected input that must be passed by convention, no declaration control, and properties are all "Global". Even parameters used in a small snippet are Global. Hopefully you can see the ramifications of this without me spelling it out.

While NAnt and MSBuild are both extensible, it can be awkward to Extend in a way providing backwards compatibility (see comments on parameters) if you are trying to build a library of scripts. It's VERY easy to break dependent scripts.

With this said, MSBuild still has a future with us. If you want to make build processes that execute with Team Build AND when a developer presses F5, MSBuild is what will facilitate it. In short, it the best way to extend Visual Studio for the foreseeable future.

I still have a temptation to use MSBuild for deployments for this simple advantage - it's part of the Framework starting in 2.0. If a machine has 2.0+ installed, it has the core MSBuild language. This gives you flexibility of pushing scripts to a target server and executing them remotely. Nonetheless, PS is so much more powerful when it comes to working with OS components and it will become standard on the OS, it's just a matter of time before you'll be able to count on availability.

Powershell not only provides Parametrized Procedures but is embraces OO with a vengeance. It naively can work will all .Net Classes, but also ADO, WMI, and COM! You don't have to dance through hoops to get full access to these. If you need to utilize .NET objects, you don't have to write rapper Tasks like you would in NAnt and MSBuild. You may want isolate the access in scripts and Cmdlets, but it's not required.

With all that said, Powershell is "scripting". You don't get strong typing that comes from a compiled language because these things are resolved at run time. It works really hard to make sense of Types but that leaves it resolution at Run Time. Also, PowerShell has new syntax and parsing rules you'll have to learn. It won't be painless to learn and master but it has so much power it will be worth it.

Since that time, we have moved forward on this plan.  Our Deployments are all using PowerShell.  Omar's article is nice but it really only scratches the surface of what we are doing.  No wonder he can call it "Deployments made simple"!
 
I'm still enjoying PowerShell and learning more stuff every day.  We really have not reached a full version 1.0 of our PowerShell deployments replicating all the features used in our NAnt deployments.  But it's real close.  The major difference is we don't customize a single PowerShell script for a product Deployment.  Everything is contained in an XML Configuration file.  With NAnt, I often had a dozen custom Build files for a product.
 
After Version 1.0, I'll start refactoring some large chunks of code into C# libraries called from PowerShell.  Much of the reasons are in my comments above.  Application code belongs in C# where I have strong typing and can have Unit Tests with Code Coverage.  PowerShell is great at some things - piping, consistent command line interface, and portable.  It also provides the ability to hot fix some logic in a pinch.
 
I'll post more about the capabilities and functionality in future Blog entries.  It's really cool!
Saturday, 13 October 2007 23:49:22 (GMT Daylight Time, UTC+01:00)  #    Comments [0] -
Powershell

This morning I did a little exercise.  Because PowerShell does not have C#'s Using Statement, Class references have to be fully qualified.  This includes Enumeration Values.  IMHO, this can make calls to managed methods a bit wordy.

In order to manage this, I have at times initialized a hash table with enumeration values.  For example, to initialize the values of System.Security.Cryptography.CipherMode I would create the following.


$CipherMode = @{}
$CipherMode.OFB = [System.Security.Cryptography.CipherMode]::OFB
$CipherMode.CFB = [System.Security.Cryptography.CipherMode]::CFB
$CipherMode.CBC = [System.Security.Cryptography.CipherMode]::CBC
$CipherMode.ECB = [System.Security.Cryptography.CipherMode]::ECB
$CipherMode.CTS = [System.Security.Cryptography.CipherMode]::CTS



This allows use of $CipherMode.CBC instead of [System.Security.Cryptography.CipherMode]::CBC.  While I found this handy at times, it is arduous to initialize.  And this one only has 5 values.

I developed a couple of functions that take the drudgery out of it.  The usage is:


$CipherMode = Get-EnumValues "System.Security.Cryptography.CipherMode"


This as easily handles enumeration of 2 or 200.  Beauty!

I also made a helper function that will simply show me the enumeration names:

Get-EnumNames "System.Security.Cryptography.CipherMode"

This will return:

CBC
ECB
OFB
CFB
CTS


The Source Code is as follows:


function Get-ValidEnumClass ([string] $ClassName = ${Throw "Class Name is required"})
{
  $type = [System.Type]::GetType($ClassName)
  if ($type -eq $null)
  {
    throw "Invalid Class Name or Assembly not loaded for [$ClassName]"
  }
  if (!$type.IsEnum)
  {
    throw "Invalid Enum Class [$ClassName]"
  }
  $type
}

function Get-EnumNames ($EnumClass = ${Throw "Valid Enum Class Name or Type is required"})
{
  if ($EnumClass -is [string])
  {
    $EnumClass = Get-ValidEnumClass $EnumClass
  }
  [
System.Enum]::GetNames($EnumClass)
}

function Get-EnumValues ([string] $EnumClassName = ${Throw "Valid Enum Class Name is required"})
{
  $type = Get-ValidEnumClass $EnumClassName
  $enumNames = Get-EnumNames $type
  $values = @{}
 
  foreach
($name in $enumNames)
  {
    $values[$name] = $type::$name.GetHashCode()
  }
  $values
}

Saturday, 13 October 2007 22:36:50 (GMT Daylight Time, UTC+01:00)  #    Comments [1412] -
Powershell

For some reason, modern error handling was left out of PowerShell Version 1.0.  However, as both a tribute and a mockery to what is in and not in PowerShell, Adam Weigert posted the following PowerShell function in this post:

function Try
{
    param
    (
        [ScriptBlock]$Command = $(throw "The parameter -Command is required."),
        [ScriptBlock]$Catch   = { throw $_ },
        [ScriptBlock]$Finally = {}
    )
   
    & {
        $local:ErrorActionPreference = "SilentlyContinue"
       
        trap
        {
            trap
            {
                & {
                    trap { throw $_ }
                    &$Finally
                }
               
                throw $_
            }
           
            $_ | & { &$Catch }
        }
       
        &$Command
    }

    & {
        trap { throw $_ }
        &$Finally
    }
}

# Example usage 

Try {
    echo " ::Do some work..."
    echo " ::Try divide by zero: $(0/0)"
} -Catch {
    echo "  ::Cannot handle the error (will rethrow): $_"
    #throw $_
} -Finally {
    echo " ::Cleanup resources..."
}

I did a bit of testing with nested error handling and I'm comfortable enough I'm going to start rolling it out.

Don't get me wrong, I LOVE PowerShell and for a version 1 product, it is amazingly functional and usable.  But, I usually refer to the error handing as Trap Crap.  It's not that it doesn't work... This function uses it and it works.  But keep in mind, implementing your own Try Finally Catch using Trap Crap would look very similar to the Try implementation code!  Can your read it?  I can, but it takes too many brain cells and productive minutes to do so.

Compare it to the "Example usage".  Hmmm... which do you want to use.  It has a bit of awkwardness because of the "parameter" nature of the scriptblocks.  Stick with the "} -Finally {" formatting.  You'll have to do that or use continuation marks.

Until 2.0 comes out, this'll have to do.

Saturday, 13 October 2007 06:19:40 (GMT Daylight Time, UTC+01:00)  #    Comments [11] -
Powershell
# Wednesday, 10 October 2007

PowerGUI's editor is nice and lightweight.  I wasn't crazy about the colors in the syntax highlighting.

Luckily, this can be customized.  Not with a GUI, but at least in the configuration XML - PowerShellSyntax.xml.  I replaced the Styles section with the following:


      <Styles>
<Style Key="ReservedWordStyle" ForeColor="Blue" Bold="True" /> 
<Style Key="OperatorStyle" ForeColor="Red" />
<Style Key="OperatorWordStyle" ForeColor="Red" />
<Style Key="VariableStyle" ForeColor="Purple" /> 
<Style Key="CmdletStyle" ForeColor="Black" Bold="True" /> 
<Style Key="NetClassStaticStyle" ForeColor="teal" /> 
<Style Key="NetClassStaticMethodStyle" ForeColor="saddlebrown" /> 
<Style Key="CmdletParamStyle" Italic="True" ForeColor="Black" /> 
<Style Key="NumberStyle" ForeColor="Black" /> 
<Style Key="StringDelimiterStyle" ForeColor="Green" /> 
<Style Key="StringDefaultStyle" ForeColor="Green" Bold="True" /> 
<Style Key="CommentDelimiterStyle" ForeColor="Maroon" /> 
<Style Key="CommentDefaultStyle" ForeColor="Maroon" /> 
<Style Key="AutoVars" ForeColor="Navy" />
</Styles>


A Sample Render looks like:


#########################################################################
# Some sample code

$strComputer = "."
$colItems = get-wmiobject -class "Win32_LoadOrderGroup" -namespace "root\CIMV2" `
   
-computername
$strComputer

foreach ($objItem in $colItems)
{
   write-host
"DriverEnabled: " $objItem.DriverEnabled 
   write-host
"GroupOrder: " $objItem.GroupOrder 
   write-host
"Name: " $objItem.Name write-host
}


I'm sure I'll tweak it, but I'm already happier using the product.

 

Wednesday, 10 October 2007 04:41:49 (GMT Daylight Time, UTC+01:00)  #    Comments [4] -
Powershell

When I first saw PowerGUI I was suspicious.  Quest is a first rate company that is very proud of their tools.  I've personally drove the purchase of their tools for Oracle Development a few years back.

Quest made a Press Release for PowerGUI.  It doesn't really commit to keeping it free.  Besides, it's still Beta.  I figured the other foot would drop when it got released.  And by foot, I mean several hundred dollars of foot.

Dimitry Sotnikov recent post announces a new download - that's still Free!  Still slightly leery, I did some searching and found his commentary Why is Quest doing free PowerShell stuff?

While I can't imagine Quest committing to keeping it free, it looks like it is for now!

Don't get me wrong, I'm not a "profit is evil" kind of guy.  But I find it difficult to keep up with the license management for my team.  A product has to be core to the job and save a magnitude more time that it costs fooling with it.  Who knows, we might get addicted to it and maybe Quest will charge a hundred dollars or less for it.

Hopeful Microsoft will make the definitive editor for PowerShell.  Hey, maybe integrate into Visual Studio!  We pay thousands of dollars per developer per year and you would think an intellisense editor for PowerShell wouldn't be too much to ask for.  (I'm hoping Bruce will slap me down again and show me where I've missed it.)

Wednesday, 10 October 2007 01:54:23 (GMT Daylight Time, UTC+01:00)  #    Comments [48] -
Powershell
# Tuesday, 09 October 2007
Simple technique for deferred expansion of Powershell strings. It is useful for Template strings.
Tuesday, 09 October 2007 19:50:06 (GMT Daylight Time, UTC+01:00)  #    Comments [48] -
Powershell
Archive
<2017 April>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2017
Cash Foley
Sign In
Statistics
Total Posts: 20
This Year: 0
This Month: 0
This Week: 0
Comments: 5994
Themes
Pick a theme:
All Content © 2017, Cash Foley
DasBlog theme 'Business' created by Christoph De Baene (delarou)