Archive for February, 2008
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.
4 Comments »
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
5 Comments »
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
No Comments »
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.
No Comments »
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
No Comments »
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
2 Comments »
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!
3 Comments »
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.
3 Comments »
In C# you can build an Enum that essentially makes your code much easier to read.
This is especially useful for bit flags where you want to represent something that is quite readable but in the background there could be all kinds ugly data that is not easy to remember.
We can do something similar in PowerShell using a hash table. For example, lets say we have a set of temperatures that we want to refer to throughout a script.
$temps = @{}
$temps.freezing = 32
$temps.boilng = 212
$temps.absoluteZero = −459.67
$temps.comfortable = 72
From here you you could take a variable $temp and say something like
if ($temp -lt $temps.freezing) {"Watch for Snow!"}
if ($temp -lt $temps.absoluteZero) {"You broke physics"}
This just may be a way to make scripts a bit more readable and easier to update after the fact.
Andy
No Comments »
|