Using System.Char Static Methods

Event 5 in the Scripting Games dealt with testing whether or not a password is secure or not.  Among many qualifications, the password had to contain at least one digit, at least one upper case character and at least one lower case character. There also needed to be a check to see if the password contained any non-alphanumeric characters, such as $%^ or (.

All these could be dealt with using regular expressions, but regex skills are weak at best, so I always default to check and see if if someone has done it for me already. Sure enough, there are a bunch of static methods for System.Char that we can use.

   1: [char]::IsUpper()
   2: [char]::IsLower()
   3: [char]::IsDigit()
   4: [char]::IsLetterOrDigit()

From these we can build a filter that will pass the string if it meets the criteria. For the sake of example, lets build "Select-ContainsUpper"

   1: # Create a filter that can be used in a pipeline
   2: filter Select-ContainsUpper {
   3:     
   4:     # coerce the string into an array of chars and then pass each one to IsUpper
   5:     # IsUpper returns $TRUE or $FALSE for each char
   6:     # So ContainsUpper now contains an array of a bunch of Booleans
   7:     $containsUpper = $_.toCharArray() | % {[char]::IsUpper($_)}
   8:     
   9:     # if any char was uppercase, some element in ContainsUpper will be $TRUE
  10:     if ($containsUpper -contains $TRUE) {$_}
  11:     }
  12:     
  13: "one","Two","5","ContainsUpperCase","all-lower-case" | Select-ContainsUpper

Building Select-ContainsDigit and the others is left as an exercise to the reader.

You could also quite easily build a function that takes the string as a password, but lately I have been big on using filters and then putting them all together in a nice pipeline for the sake of clarity and a nice overall PowerShelly look.

Creating an Array of PowerShell Custom Objects

During the Scripting Games I found myself creating a lot of custom objects with properties that I could use to sort , select, take averages of, and a number of other cool things. Getting results into a Powershell object can make life a lot easier for a number of reasons.

There was one little piece I was missing. Not only did I want to create a single object, quite often I would want to put all the objects I created into a collection of objects. Did you know you can add collections of like objects to each other ?

   1: PS 13 >  $a = get-process

   2: PS 14 >  $a.count

   3: 66

   4: PS 15 >  $b = get-process

   5: PS 16 >  $c = $a + $b

   6: PS 17 >  $c.count

   7: 131

   8: PS 18 >

The code above shows that I can add two collections of process objects together. Very cool.

So I tried doing this in the scripting games and came across a problem. For instance, in Event 3 we needed to tally up a bunch of votes. So what I really needed was to create a bunch of $vote objects and put them all together in a collection called $votes

Here's what I came up with at first.

By the way, when I create PS Custom Objects I cheat and use the "" | Select-Object prop1, prop2 nomenclature. My easier than using new-object followed by a bunch of add-member commands.

   1: $votes = "" | Select-Object v1,v2,v3,v4

   2: foreach ($v in Get-Content votes.txt)

   3:     {    

   4:         $vote = "" | Select-Object v1,v2,v3,v4;

   5:         $vote.v1,$vote.v2,$vote.v3,$vote.v4 = $v.split(",")

   6:         $votes += $vote

   7:     } 

Looks nice and shiny until you run it :) I get the following error:

Method invocation failed because [System.Management.Automation.PSObject] doesn't contain a method named 'op_Addition'.

Not so shiny

The trick is that we $votes needs to be a collection of $vote objects, not another object identical to $vote.

So we instantiate $votes with a cast to [array] and life is good.

   1: $votes = @()

   2: foreach ($v in Get-Content votes.txt)

   3:     {    

   4:         $vote = "" | Select-Object v1,v2,v3,v4;

   5:         $vote.v1,$vote.v2,$vote.v3,$vote.v4 = $v.split(",")

   6:         $votes = $votes + $vote

   7:     } 

A quick update, thanks to Aleksandar. We should instantiate $votes as $votes = @(). I have updated the example above.

Scripting Games Event 6 Select-Prime

Event six was a classic programming math problem. Find all the prime numbers in a given range. For this particular problem, the range was between 1 and 200.

Here's my answer

   1: filter select-prime 
   2: { 
   3:     if ($_ -eq 1) {return $null}
   4:     for ($i=2;$i -le ([int][Math]::Sqrt($_));$i++) 
   5:         { 
   6:         if ($_ % $i -eq 0 ) {return}
   7:         }
   8:     $_
   9: } 
  10: 1..200 | select-prime

It turns out that in order to find if a prime number we can use the modulus operator. This math operator simply returns the remainder when one number is divided by another

If a number n % x  = 0 where x is not 1 or n, then the number will not be prime.

   1: PS U:> 6 % 2
   2: 0
   3: PS U:> 5 % 3
   4: 2
   5: PS U:> 5 % 2.5
   6: 0
   7: PS U:> 9 % 2
   8: 1
   9: PS U:> 9 % 3
  10: 0

So this will work if we just went from 2 to N. But it turns out that is way more work than necessary. We only need to go up to the Square Root of N and we can call it prime.

I really thought I was being super cool and iterated up the square root of the number. Turns out the calls into .NET to calculate the square root were to costly to make my efficiency efficient.

   1: PS C:usersandysDesktop> cat C:usersandysDesktopprimes.ps1
   2: filter select-primeQuickly
   3: {
   4:         if ($_ -eq 1) {return $null}
   5:         for ($i=2;$i -le ([int][Math]::Sqrt($_));$i++)
   6:                 {
   7:                 if ($_ % $i -eq 0 ) {return}
   8:                 }
   9:         $_
  10: }
  11:  
  12: filter select-primeSlowly
  13: {
  14:         if ($_ -eq 1) {return $null}
  15:         for ($i=2;$i -le $_;$i++)
  16:                 {
  17:                 if ($_ % $i -eq 0 ) {return}
  18:                 }
  19:         $_
  20: }
  21:  
  22: "Quickly"
  23: ""
  24: measure-command {1..200 | select-primeQuickly}
  25: "Slowly"
  26: ""
  27: measure-command {1..200 | select-primeSlowly}
  28: PS C:usersandysDesktop> C:usersandysDesktopprimes.ps1
  29: Quickly
  30:  
  31: Days              : 0
  32: Hours             : 0
  33: Minutes           : 0
  34: Seconds           : 0
  35: Milliseconds      : 734
  36: Ticks             : 7346876
  37: TotalDays         : 8.5033287037037E-06
  38: TotalHours        : 0.000204079888888889
  39: TotalMinutes      : 0.0122447933333333
  40: TotalSeconds      : 0.7346876
  41: TotalMilliseconds : 734.6876
  42:  
  43: Slowly
  44:  
  45: Days              : 0
  46: Hours             : 0
  47: Minutes           : 0
  48: Seconds           : 0
  49: Milliseconds      : 346
  50: Ticks             : 3469459
  51: TotalDays         : 4.0155775462963E-06
  52: TotalHours        : 9.63738611111111E-05
  53: TotalMinutes      : 0.00578243166666667
  54: TotalSeconds      : 0.3469459
  55: TotalMilliseconds : 346.9459

Looking at the results, what I thought was going to be quick was actually a lot slower! The version that calls into .NET ran in 736 milliseconds and the version that did not ran in 346 milliseconds.

Goes to show that when you are programming or scripting, what we think may be a gain in efficiency, may very well not be, as I learned in this exercise.

Powershell Function Start-Proc

The gentlemen over at the Power Scripting Podcast recently posted a tip on how to start processes in Powershell.

I had run into the same problem they had. How do you pass in both an executable and its arguments to the Start method of a System.Diagnostics.Process object.

Something like this works fine in Powershell:

[System.Diagnostics.Process]::Start("calc")

But as soon as you try to pass in something like "ipconfig /all" the thing blows up.

Turns out you can pass in arguments a couple of ways. The Powerscripting guys noted that if you pass in the argument of the executable as a second argument to Start, it will work great.

[System.Diagnostics.Process]::Start("ipconfig","all")

However, there is another way if you want to get a little bit more fancy and do things like hide windows or redirect output from StdOut or StdErr.

You can use a System.Diagnostic.ProcessStartInfo object. Using get-member we can take a look at all the options we have for such a thing.

   1: PS 124 >  $si = New-Object System.Diagnostics.ProcessStartInfo
   2: PS 125 >  $si | gm  -type property | select Name
   3:  
   4: Name
   5: ----
   6: Arguments
   7: CreateNoWindow
   8: Domain
   9: EnvironmentVariables
  10: ErrorDialog
  11: ErrorDialogParentHandle
  12: FileName
  13: LoadUserProfile
  14: Password
  15: RedirectStandardError
  16: RedirectStandardInput
  17: RedirectStandardOutput
  18: StandardErrorEncoding
  19: StandardOutputEncoding
  20: UserName
  21: UseShellExecute
  22: Verb
  23: Verbs
  24: WindowStyle
  25: WorkingDirectory
Lots of goodness here that we can play with. Once you build up the ProcessStartInfo object, you pass that whole object in as the arg to the Start method of system.diagnostics.process.

I put together a quick function to show how this could be used more generically.

   1: function Start-Proc  {
   2:     param (
   3:             [string]$exe = $(Throw "An executable must be specified"),
   4:             [string]$arguments,
   5:             [switch]$hidden,
   6:             [switch]$waitforexit
   7:             )    
   8:     
   9:     # Build Startinfo and set options according to parameters
  10:     $startinfo = new-object System.Diagnostics.ProcessStartInfo 
  11:     $startinfo.FileName = $exe
  12:     $startinfo.Arguments = $arguments
  13:     if ($hidden){
  14:                 $startinfo.WindowStyle = "Hidden"
  15:                 $startinfo.CreateNoWindow = $TRUE
  16:                 }
  17:     $process = [System.Diagnostics.Process]::Start($startinfo)
  18:     if ($waitforexit) {$process.WaitForExit()}
  19:     
  20: }
  21:  
  22:  Start-Proc calc
  23:  Start-Proc calc -waitforexit
  24:  Start-Proc -exe ipconfig -arguments /all
  25:  Start-Proc ipconfig /all
  26:  Start-Proc ipconfig /all -hidden

I used the alias Start-Proc so that it would not collide with the PSCX Cmdlet Start-Process.

They have done the same writing their CmdLet  in C#, but with all kinds of options.

   1: PS 130 >  gcm Start-Process | fl *
   2:  
   3:  
   4: DLL              : C:\Program Files (x86)\PowerShell Community Extensions\Pscx.dll
   5: Verb             : Start
   6: Noun             : Process
   7: HelpFile         : Pscx.dll-Help.xml
   8: PSSnapIn         : Pscx
   9: ImplementingType : Pscx.Commands.StartProcessCommand
  10: ParameterSets    : {[[-Path] <String>] [[-Arguments] <String>] [-Verb <String>] [-WorkingDirectory <String>] [-Credential <PSCredential>] [-NoShellExecute] [-NoWindow]
  11:                    [-WindowStyle <ProcessWindowStyle>] [-LoadUserProfile] [-WaitTimeout <Int32>] [-Boost] [-Priority <ProcessPriorityClass>] [-Verbose] [-Debug] [-Error
  12:                    Action <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm], [[-ScriptBlock] <ScriptBlock>
  13:                    ] [-NoProfile] [-WorkingDirectory <String>] [-Credential <PSCredential>] [-NoShellExecute] [-NoWindow] [-WindowStyle <ProcessWindowStyle>] [-LoadUser
  14:                    Profile] [-WaitTimeout <Int32>] [-Boost] [-Priority <ProcessPriorityClass>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-ErrorVariable <St
  15:                    ring>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]}
  16: Definition       : Start-Process [[-Path] <String>] [[-Arguments] <String>] [-Verb <String>] [-WorkingDirectory <String>] [-Credential <PSCredential>] [-NoShellExecute]
  17:                     [-NoWindow] [-WindowStyle <ProcessWindowStyle>] [-LoadUserProfile] [-WaitTimeout <Int32>] [-Boost] [-Priority <ProcessPriorityClass>] [-Verbose] [-D
  18:                    ebug] [-ErrorAction <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
  19:                    Start-Process [[-ScriptBlock] <ScriptBlock>] [-NoProfile] [-WorkingDirectory <String>] [-Credential <PSCredential>] [-NoShellExecute] [-NoWindow] [-W
  20:                    indowStyle <ProcessWindowStyle>] [-LoadUserProfile] [-WaitTimeout <Int32>] [-Boost] [-Priority <ProcessPriorityClass>] [-Verbose] [-Debug] [-ErrorAct
  21:                    ion <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>] [-WhatIf] [-Confirm]
  22:  
  23: Name             : Start-Process
  24: CommandType      : Cmdlet
  25: Visibility       : Public

Scripting Games 2008 Event 2

I thought event 2 was pretty cool.  We have to take in a list of figure skaters and their scores, drop the highest and lowest score, average them, and assign a gold, silver and bronze medal to the top three competitors.

This problem was extremely conducive to using the power of the pipeline in Powershell.

   1: $competitors = Get-Content c:\scripts\skaters.txt 
   2: [array]$totalScores = "" | Select-Object Name,Score,Medal
   3:  
   4: foreach ($competitor in $competitors)
   5: {
   6:     $scores = "" | Select-Object Name,Score,Medal
   7:     $scores.Name,$scores.Score = $competitor.Split(",")
   8:     $stats = $scores.Score | Measure-Object -Maximum -Minimum
   9:     $scores.Score = $scores.Score -replace $stats.Maximum
  10:     $scores.Score = $scores.Score -replace $stats.Minimum
  11:     $scores.Score = [math]::Round(($scores.Score | Measure-Object -Average).Average,2)
  12:     $totalScores += $scores
  13: }
  14: $winners = $totalScores | Sort-Object Score -Descending | Select-Object -First 3 $winners[0].Medal,$winners[1].Medal,$winners[2].Medal = "Gold","Silver","Bronze"
  15: $winners | Format-Table -AutoSize

First we pull in the contents of the skaters.txt file. Next I use a little shortcut to create a custom PSObject using select-object. This creates a PSObject with three properties, a Name, a Score, and a Medal. This will become a collection of scores objects.

Using a foreach loop, we go through each competitor and create a new score object, assigning its values.Name to the name of the competitor and then the scores as an array in the .score property.

Please check out my post on multi variable assignment to see how this works.

I can pull the minimum and maximum using measure object and drop them. After that I use measure-object again to take the average and stuff it in the .score property.

I should mention here that /\/\o\/\/'s solution was pretty darn slick. I loved how he sorted and then select 1..5, essentially dropping the top and bottom score before he took the average.

The final item is to add the current score object to the collection of score objects called $totalScores.

Now it becomes quite easy to get the winners. We just sort the $totalScores collection by Score, select the top 3, and assign their .medal value accordingly.

Multi variable assignment is so cool. Ever since I found out about it it seems to appear in everything I do.

 

Have fun Powershelling

 

Andy

Scripting Games 2008 Event 1

Now that the answers have been posted for the first 2 events, I thought I would share my answer for the first event.

My first version worked but on some queries would take up to 20 plus minutes to crunch to through the 32,000 + words.

Here is what I finally ended up with:

   1: $wordlistlocation = "c:\scripts\wordlist.txt"
   2: $phonenumber = Read-Host "Please Enter a phone number"
   3: $numbers = [char[]]$phonenumber.ToString()
   4:  
   5: filter Get-Letters {
   6:  
   7:     switch ($_) {
   8:         2 {$letters = "ABC"}
   9:         3 {$letters = "DEF"}
  10:         4 {$letters = "GHI"}
  11:         5 {$letters = "JKL"}
  12:         6 {$letters = "MNO"}
  13:         7 {$letters = "PRS"}
  14:         8 {$letters = "TUV"}
  15:         9 {$letters = "WXY"} 
  16:     } # Switch
  17:  
  18:     return $letters
  19:  
  20: } #Get-Letters
  21:  
  22: $first,$second,$third,$fourth,$fifth,$sixth,$seventh = $numbers | Get-Letters
  23:  
  24: $string1 = $first[0]     + $first[1]     + $first[2]
  25: $string2 = $second[0]     + $second[1]     + $second[2]
  26: $string3 = $third[0]     + $third[1]     + $third[2]
  27: $string4 = $fourth[0]    + $fourth[1]    + $fourth[2]
  28: $string5 = $fifth[0]    + $fifth[1]        + $fifth[2]
  29: $string6 = $sixth[0]    + $sixth[1]        + $sixth[2]
  30: $string7 = $seventh[0]    + $seventh[1]    + $seventh[2]
  31:  
  32: $pattern = "^[" + $string1 + "][" + $string2 + "][" + $string3 + "][" + $string4 +"][" + $string5 + "][" + $string6 + "][" + $string7 + "]"
  33:  
  34: Get-Content $wordlistlocation | Select-String -Pattern $pattern | Select -first 1

First I get the content of the wordlist file and then read in a phone number. The cast to an array of char[] was necessary to put them into an array as individual elements. It was interesting that I could not cast to an array of ints, but time was short and I was under pressure, and it worked :)

Next, I used switch to build a filter that returns the possible letters for any given number. If I pass in a 2,  I get back a string of ABC.

Next I used multiple assignment to get all the possibilities for each number.

I used these to build a big ol' regex pattern. It's long but relatively simple. Basically just creates 7 chars using the [] for "this OR that OR the other thing."

For example, if the first two numbers were 2 and 3, it would generate [abc][def] as the pattern.

Once I built up that pattern, I simply used get-content of the word list and piped that to Select-String and gave Select-String the the regex pattern I created.

In my first attempt, I created a list of all possibilities and then did a -match on them and compared to the word list file.

Using regex and Select-String brought my execution time down from 23 minutes to about 7 seconds, give or take.

The 411 on Powershell Operators &#8211; Part 1 the -eq operator

Holy Schnikees there are a lot of operators in Powershell! This may not be an exact count  but looking through the help file it looks like there are 57 operators!  I am using the CTP - Powershell V2 -  which introduced a handful of new operators, particularly  -join and -split.

I'd like to start a series on some of the operators and how they can be used. I'd like to start with the most basic of operators, the -eq operator. This is the equality operator.

Note that this is different than the assignment operator "=" which is used to assign values to variables.

In a lot of programming languages, the "==" operator is used for equality.  Here's a quick example.

   1: PS C:\Users\andys> 1 -eq 1

   2: True

   3: PS C:\Users\andys> 2 -eq 3

   4: False

This is pretty normal, (and boring). Do notice that this returns the boolean value True.

But what about if we compare an array  to an integer.

   1: PS C:\Users\andys> function new-array {,$args}

   2: PS C:\Users\andys> $i = new-array 1 2 3 4 5

   3: PS C:\Users\andys> $i

   4: 1

   5: 2

   6: 3

   7: 4

   8: 5

   9: PS C:\Users\andys> $i -eq 3

  10: 3

Interesting that the value that is returned is the value of the item in the array that was a match. It was NOT a boolean. You can read about my new-array function here for more info, but it allows you to create an array with a lot less typing.

The same is true with strings.

   1: PS C:\Users\andys> $s = new-array one two cat dog

   2: PS C:\Users\andys> $s -eq "cat"

   3: cat

   4: PS C:\Users\andys> $s -eq "not-here"

   5: PS C:\Users\andys>

However, if we use this in an if statement, it will get converted to a boolean value.

   1: PS C:\Users\andys> if ($s -eq "cat") {return $true} else {return $false}

   2: True

   3: PS C:\Users\andys> if ($s -eq "no") {return $true} else {return $false}

   4: False

   5: PS C:\Users\andys>

We can use an implicit cast to show this:

   1: PS C:\Users\andys> [bool]($s -eq "cat")

   2: True

   3: PS C:\Users\andys> [bool]($s -eq "not here")

   4: False

   5: PS C:\Users\andys>

I am continuously amazed at how deep you can go with exploring Powershell and looking at subtle nuances such as these. It's really pretty awesome to think about how all these operators just work as you expect, given the context you are using them in.

 

Andy

I object to ipconfig

The beauty of Powershell is that everything in Powershell is an object. You take objects, pass them to objects, and get objects back. One of the other great features of Powershell is compatibility with older CMD commands. The problems arises though when we want to get the two working together.

A big issue with regular commands is the each one has its own way of displaying output. Interpreting and using this output programmatically can be tricky at best. Mostly for the sake of an example, I'd like to walk through the process of objectifying the output of ipconfig.

To begin, we will need to run ipconfig and store the output in a variable. However, when we run it, lets filter down as much as possible with some broad parsing and then we can filter it down and fine tune our parsing as we get closer to the actual information we want.

So here we go:

   1: PS C:\> $ip = ipconfig | Select-String "IPv4|Subnet|Gateway" | select-object -first 3
   2: PS C:\> $ip
   3:  
   4:    IPv4 Address. . . . . . . . . . . : 192.168.15.220
   5:    Subnet Mask . . . . . . . . . . . : 255.255.255.0
   6:    Default Gateway . . . . . . . . . : 192.168.15.1
   7:  
   8:  
   9: PS C:\>

Using select-string we can get all the lines that have "IPv4 OR Subnet OR Gateway." Next we pipe this to select-object and select the first 3 lines that match our criteria. This way we are only getting the first adapter.

Now that we have this info store in the variable $ip we can start looking at parsing out the different lines. Lets just look at working with the first line as a demonstration.

   1: PS C:\> $ip[0]
   2:  
   3:    IPv4 Address. . . . . . . . . . . : 192.168.15.220
   4:  
   5: # Use multi variable assignment to get the output of the split on the string
   6: # Note that we split on ":" so we IPv4 Address set to $prop and the IP Address 
   7: # gets set to $value
   8:  
   9: PS C:\> $prop,$value = $ip[0].ToString().Split(":")
  10: PS C:\> $prop
  11:    IPv4 Address. . . . . . . . . . .
  12: PS C:\> $value
  13:  192.168.15.220
  14:  
  15: PS C:\> # Now lets clean it up a bit
  16:  
  17: PS C:\> $prop.Trim(" :")
  18: IPv4 Address. . . . . . . . . . .
  19:  
  20: # Trim off the spaces and periods.
  21: PS C:\> $prop.Trim(". :")
  22: IPv4 Address
  23:  
  24: # for this we can just trim off the leading space
  25:  
  26: PS C:\> $value
  27:  192.168.15.220
  28: PS C:\> $value.Trim()
  29: 192.168.15.220

We use the split method to extract the property and value, essentially cutting each line in half at the ":"

Once we get the individual property and value entries we can clean them up with a little haircut... using the Trim method.

The last thing we will need to do is add these properties and their values to an object. This is accomplished using the add-member cmdlet.

   1: PS C:\> $prop = $prop.Trim(". :")
   2: PS C:\> $prop
   3: IPv4 Address
   4: PS C:\> $value = $value.Trim()
   5: PS C:\> $value
   6: 192.168.15.220
   7: PS C:\> $ipo = New-Object psobject
   8: PS C:\> Add-Member -InputObject $ipo noteproperty -Name $prop -Value $value
   9: PS C:\> $ipo
  10:  
  11: IPv4 Address
  12: ------------
  13: 192.168.15.220
  14:  
  15:  
  16: PS C:\>


Now if we wrap all this up into a foreach-object command.

   1: PS C:\> $ip = ipconfig | Select-String "IPv4|Subnet|Gateway" | select-object -first 3
   2: PS C:\> $ip | % {$ipo = New-Object psobject} {$prop,$value = $_.ToString().Split(":");
   3: >> Add-member -InputObject $ipo noteproperty -Name $prop.Trim(". :") -Value $value.Trim()}
   4: >>
   5: PS C:\> $ipo
   6:  
   7: IPv4 Address                                              Subnet Mask                                               Default Gateway
   8: ------------                                              -----------                                               ---------------
   9: 192.168.15.220                                            255.255.255.0                                             192.168.15.1
  10:  
  11:  
  12: PS C:\> $ipo | fl *
  13:  
  14:  
  15: IPv4 Address    : 192.168.15.220
  16: Subnet Mask     : 255.255.255.0
  17: Default Gateway : 192.168.15.1
  18:  
  19:  
  20:  
  21: PS C:\>

Really the only tricky thing here is that we changed $ip[0] to $_ so we could process the current object each time using the "%" which is the alias for the ForEach-Object cmdlet..

This is all sweet and nice but still its a lot of work for one command and doing all the string manipulation is  a bit tough, and not repeatable for other commands. So this brings up a question for which I do not have an answer. in the long run,  do we adopt commands and coerce them into the world of objects or do we build functions/Cmdlets to replace them.

I am sure the answer is the same as what my grandpa said when I asked him if he wears briefs of boxers... "Welll.. Depends"

What say you ?

Andy

Powershell function New-Array

Powershell can slice and dice arrays all day long, and we use them all the time. However, when we are testing stuff and creating dummy arrays the syntax can be a bit awkward. Its quite straightforward and understandable, but its just a pain to type

For example

   1: PS 39 >  $i = "one","two","three"
   2: PS 40 >  $i
   3: one
   4: two
   5: three

Now for me that is just way too many quotes and commas. This can easily be addressed with a new function called New-Array.  Are we going to have to parse and add quotes and do all kinds of crazy stuff to get this working ? Not really. Here's the function and how to use it.

   1: PS 41 >  function new-array {$args}
   2: PS 42 >  $i = new-array one two three four five six
   3: PS 43 >  $i
   4: one
   5: two
   6: three
   7: four
   8: five
   9: six

Ah, now that is much better. All this does is return the $args array which is an array that you get in every function be default. $args[0] is the first argument. $args[1] is the second etc etc.

That being said we are not quite there.  Lets say you want to be really sure you create an Array. In the case where you originally have only 1 $arg it will return a scalar. Let me demonstrate.

   1: PS 58 >  function new-array {$args}
   2: PS 59 >  $i = new-array 1
   3: PS 60 >  $i += 2
   4: PS 61 >  $i
   5: 3

 

In this case, $i ends up being a scalar with a value of 1 and then when you add 2 to it, you get $i = 3.

What we want is to be able to use the += operator to add a value to the array. There are multiple ways to force a scalar to be an array when you declare it but i think the comma operator is the shortest.  So we make one quick addition to our new-array function.

   1: PS 62 >  function new-array {,$args}
   2: PS 63 >  $i = new-array 1
   3: PS 64 >  $i +=2
   4: PS 65 >  $i
   5: 1
   6: 2

Pretty cool!

Multi Variable Assignment in Powershell

An extremely powerful feature (that isn't all that well known) is the ability to assign values to multiple variables in one line. Take the following example:

PS> $one,$two,$three = "first","second","third"
PS> $one;$two;$three
first
second
third

This technique can be used in custom functions to parse the $args value

PS> function show-greeting {$greeting,$name = $args; "Hello $name, $greeting"}
PS> show-greeting "Nice to meet you" "Andy"
Hello Andy, Nice to meet you

One last thing to note is what happens if you have more values than variables. Does it blow up. An error. Not really. Check this out

PS C:\Users\andys> $one,$two = "first","second","third",4
PS C:\Users\andys> $one
first
PS C:\Users\andys> $two
second
third
4
PS C:\Users\andys> $one.count
PS C:\Users\andys> $two.count
3
PS C:\Users\andys>

The last variable gets the remaining values assigned to it.