Tuesday, July 01, 2008

Why do such a thing? Well, if you’ve created your own aliases for commands and you try to give someone your ps1 script file, it will not run because they have not got the same aliases defined as you. Also, scripts that use fully-resolved names like “Get-ChildItem” are more readable for a newcomer to PowerShell than one that is using the unix-like “ls" alias for example. "Get-ChildItem" leads quite directly to the MSDN documentation, but "ls" might lead you anywhere. Before you publish a script online somewhere for the world to use, it’s important that you try to remove any aliases and replace them with the native command names.

Doing this kind of thing has been talked about before but it was always a very difficult thing to do with PowerShell v1.0, what with the lack of BNF documentation describing the grammar etc. Thankfully, it's a lot easier to do with PowerShell v2.0 (currently at release CTP2) because the team has exposed the Tokenizer for use in scripts. There's been suprisingly little use of it so far, so I figured I'd start the ball rolling with a series of articles based around it. So, let's look at an example script that uses aliases and put it through the meat grinder:

image

As you can see, it spits out the expanded script to the output stream. The informational messages are written to the host, so they won't interfere if you redirect the output to a file like: .\resolve-aliases.ps1 in.ps1 > out.ps1

Here's the Resolve-Aliases.ps1 script itself:

  1. #requires -version 2  
  2. param($filename = $(throw "need filename!"))  
  3.  
  4. $lines = $null 
  5. $path = Resolve-Path $filename -ErrorAction 0  
  6.  
  7. if ($path) {  
  8.     $lines = Get-Content $path.path  
  9. } else {  
  10.     Write-Warning "Could not find $filename" 
  11.     exit  
  12. }  
  13.  
  14. # Initialize  
  15. $parser = [system.management.automation.psparser]  
  16. $errors = new-object system.management.automation.psparseerror[] 0  
  17.  
  18. do {  
  19.     $tokens = $parser::tokenize($lines, [ref]$errors)     
  20.     $retokenize = $false 
  21.       
  22.     if ($errors.count -gt 0) {  
  23.         Write-Warning "$($errors.count) error(s) found in script." 
  24.         $errors 
  25.         exit  
  26.     }  
  27.  
  28.     # look through tokens for commands  
  29.     $tokens | % {  
  30.         if ($_.Type -eq "Command") {  
  31.             $name = $_.Content  
  32.               
  33.             # is it an alias?  
  34.             # we use -literal here so '?' isn't treated as wildcard  
  35.             if ((!($name -eq ".")) -and (Test-Path -LiteralPath alias:$name)) {  
  36.                   
  37.                 # gcm may return more than one match, so specify "alias"  
  38.                 # filtering against name kludges the '?' alias/wildcard   
  39.                 $command = gcm -CommandType alias $name | ? { $_.name -eq $name }  
  40.                               
  41.                 # resolve alias which may lead to another alias  
  42.                 # so loop until we reach a non-alias  
  43.                 do {  
  44.                     $command = Get-Command $command.definition  
  45.                 } while ($command.CommandType -eq "Alias")  
  46.                   
  47.                 Write-Host -NoNewline "Resolved " 
  48.                 Write-Host -NoNewline -ForegroundColor yellow $name 
  49.                 write-host -nonewline " to "   
  50.                 write-host -ForegroundColor green $command.name           
  51.                   
  52.                 # Use a stringbuilder to replace the alias in the line  
  53.                 # pointed to in the Token object. StringBuilder has a much  
  54.                 # more precise Replace method than String. This allows us to  
  55.                 # replace the token with 100% confidence.  
  56.                 $sb = New-Object text.stringbuilder $lines[$_.startline - 1]  
  57.                 $sb = $sb.replace($name, $command.Name, $_.startcolumn - 1, $_.length)  
  58.                 $lines[$_.startline - 1] = $sb.tostring()  
  59.                   
  60.                 # now that we've replaced a token, the script needs to be reparsed  
  61.                 # as offsets have changed on this line.   
  62.                 $retokenize = $true 
  63.                   
  64.                 # break out of pipeline, (not 'do' loop)  
  65.                 continue;  
  66.             }  
  67.         }  
  68.     }  
  69. } while ($retokenize)  
  70.  
  71. Write-Host "" # blank line  
  72.  
  73. # output our modified script  
  74. $lines 

Of course, this requires PowerShell v2.0 CTP2. Next in the series, I'll give you a script to check your ps1 scripts for backwards compatibility against PowerShell 1.0. That should be handy for those naughty admins out there who despite all the warnings have installed v2 in production. ;-)

posted on Tuesday, July 01, 2008 5:43:07 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
 Friday, June 27, 2008

Someone on a private mailing list I'm on lamented the problem with powershell's '>' redirection operator defaulting inflexibly to use unicode for encoding the output file. This is not very compatible for NT's ancient console subsystem which works best with ASCII data. Fortunately, there's an easy workaround to fix this:

Due to the magic of command discovery and the fact that > really does use out-file, you can "fix" this by placing the following in your $profile:

function out-file($FilePath, $Encoding, [switch]$Append) {
$input | microsoft.powershell.utility\out-file $filepath -encoding ascii `
     -append:$append
}


From now on, > will be forced to use ASCII encoding. This works because functions have higher precedence than built-in commands in powershell's command discovery search.

UPDATE: Rather annoyingly, I'm informed that this particular workaround doesn't work on v1.0 of PowerShell. I tested the above on v2.0CTP2 only. Doh.

posted on Friday, June 27, 2008 11:03:07 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
 Wednesday, June 18, 2008

MoW poked at me, so I guess I can’t let the crazy dutchman down:

How old were you when you started using computers?

10 or 11

What was your first machine?

An Amstrad CPC 464 with Green screen.

What was the first real script you wrote?

I vaguely remember being delighted at having a rocket (Chr$(239)) ascend the Amstrad’s screen when I figured out that STEP –1 was the key in getting a for/next loop to count backwards.

What languages have you used?

    • Powershell, VBScript, JavaScript, Tcl, Perl, Batch/4NT
    • Z80A, 8088/8086 (NECv20/v30) assembler
    • Basic, Turbo Pascal, Turbo C, C++, Java, C#, VB.NET

I’m a bit of a jack-of-all-trades when it comes to development. The list above is just what I can remember ;-)

What was your first professional Sysadmin Developer gig?

After helping out a friend’s dad put together a training course for Visual Basic 3.0, I then managed to blag a job coding Ireland’s first ever major e-commerce site using MS Merchant Server 1.0 (which predated ASP 1.0) for a leading ISP.

If you knew then what you know now, would you have started in IT?

I might have started a little bit earlier even!

If there is one thing you learned along the way that you would tell new Sysadmins (or Devs), what would it be?

Just one thing? Learn Regular Expressions as it will pay you back ten-fold for whatever time you put into it.

What is the most fun you have had scripting?

There’s only one answer here: writing extensions for PowerShell.

This particular branch of the meme ends here as I don’t think I can tag anyone who hasn’t been tagged already.

posted on Wednesday, June 18, 2008 7:31:00 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Friday, June 06, 2008

In the spirit of "tidying things up," and pushing out nearly-there projects, I turned my attention to http://www.codeplex.com/PSMobile. I have a number of seriously annoying (to myself and people around me) habits, one of which is having a hard time finding the motivation to get that last 1% bit of work done. I am trying to kill bludgeon that habit, and this is fruit number #2 of that attempt at self-correction. I have some other important out of band work that I need to complete, but I find I cannot concentrate on that until these personal projects that have been niggling me for ages are dealt with. Anyway, lets dispel a couple of questions with a screenshot:

Requirements

  • ActiveSync 4.2 or higher (or Windows Mobile Device Centre 6.0+ on Vista) Download
  • A Windows Mobile device (PocketPC/SmartPhone 2002, 2003, 2003SE, Windows Mobile 5, 6 or 6.1)
  • Windows PowerShell 1.0 or 2.0 (CTP) Download

Features

wm61-device

  • Copy, Move, Delete items between folders on your device (including Storage Card) with standard PowerShell Cmdlets
  • Move/Copy files to/from your device and your desktop with ConvertTo-WMFile and ConvertFrom-WMFile
  • Get device information and manipulate and explore the registry with a rich device object returned from Get-WMDevice
  • Invoke-Item against remote items to or execute or trigger their associated applications
  • Invoke-Item with -Local switch to attempt to execute a remote file in the context of your local desktop (e.g. office docs or images/videos)
  • New "Mode" attributes specific to Windows Mobile file attributes: (I)nRom, Rom(M)odule
  • File/Folder objects' attributes can be modified with .Attributes properties just like FileInfos etc.
  • Tab completion with MoW's PowerTab Download

File Manipulation

A picture's worth a thousand words.

images-screenshot

Cmdlets and Definitions

Here's a table of the syntax for the included Cmdlets.

Cmdlet Definition WhatIf / Confirm
ConvertFrom-WMFile * [-Path] [-Destination] [-Force] [-Verbose] Yes
  [-LiteralPath] [-Destination] [-Force] [-Verbose] Yes
ConvertTo-WMFile * [-Path] [-Destination] [-Force] [-Verbose] Yes
  [-LiteralPath] [-Destination] [-Force] [-Verbose] Yes
Get-WMDeviceInfo [-Verbose]  
Get-WMMemoryInfo [-Verbose]  
Get-WMStoreInfo [-Verbose]  
Start-WMActiveSync [-Verbose]  
Stop-WMActiveSync [-Verbose]  
Start-WMProcess [-LiteralPath] [[-Arguments] ] [-Verbose]  
Get-WMDevice [-Verbose]  

* These Cmdlets that accept a path will bind to pipeline input via PSPath property name.

If you've got any problems, suggestions or ideas, please post into the discussions board on the web site. Have fun!

posted on Friday, June 06, 2008 6:00:15 AM (Eastern Standard Time, UTC-05:00)  #    Comments [5] Trackback
 Thursday, June 05, 2008

I took a few hours yesterday to "tidy up my room" so to speak, so I built a nice MSI installer, updated the help, CodePlex Wiki and examples and closed all bugs. This is probably the final release now that PowerShell 2.0 CTP2 has introduced support for eventing, so thanks for all the support.

New Features

  • Multiple named queue support and default queue with -QueueName parameter
  • Better COM support, window message pumping etc.
  • NoFlush / Peek parameter support for queue reading
  • Get-EventQueue command added for viewing queues and their message counts.

Cmdlet Name Changes

  • Get-Event -> Read-Event
  • Connect-EventListener -> Connect-Event
  • Disconnect-EventListener -> Disconnect-Event

Additionally, several niggling bugs closed (including the one where read-event -wait would return immediately with no events).

http://www.codeplex.com/pseventing

For an advanced example: Foreground / Background Swappable Downloads In PowerShell.

posted on Thursday, June 05, 2008 8:20:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Friday, May 23, 2008

UPDATE May 26th: You must run PowerShell v2.0 CTP in STA mode for this to work. Start the shell, then run "powershell -sta" from the command line to start a new version of the shell in "single thread apartment" mode (STA). This is required for WPF to work correctly.

That is a bit of a mouthful of a title for this post but it's the best I could come up with. This post takes some of James' scripty bits and Jaykul's scripty bits and shows you how to create a countdown timer written in PowerShell script that runs in the background without blocking input. Just like Jaykul's original clock, you can drag it around and right-clicking it will close it. His version was the current time and it also showed some system resources. I changed it into a countdown and removed the other nested graphs. When it hits 00:00:00 it turns red. Here's what it looks like:

countdown

Here's the source of invoke-background.ps1:

  1. param([string]$scriptName)  
  2.  
  3. # original script James Brundage (blogs.msdn.com/powershell)  
  4.  
  5. $rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()  
  6. $rs.ApartmentState, $rs.ThreadOptions = “STA”, “ReuseThread”  
  7. $rs.Open()  
  8.  
  9. # Reference the WPF assemblies  
  10. $psCmd = {Add-Type}.GetPowerShell()  
  11. $psCmd.SetRunspace($rs)  
  12. $psCmd.AddParameter("AssemblyName", "PresentationCore").Invoke()  
  13. $psCmd.Command.Clear()  
  14.  
  15. $psCmd = $psCmd.AddCommand("Add-Type")  
  16. $psCmd.AddParameter("AssemblyName", "PresentationFramework").Invoke()  
  17. $psCmd.Command.Clear()  
  18.  
  19. $psCmd = $psCmd.AddCommand("Add-Type")  
  20. $psCmd.AddParameter("AssemblyName", "WindowsBase").Invoke()  
  21.  
  22. $sb = $executionContext.InvokeCommand.NewScriptBlock(  
  23.     (Join-Path $pwd $scriptname)  
  24. )  
  25.  
  26. $psCmd = $sb.GetPowerShell()  
  27. $psCmd.SetRunspace($rs)  
  28. $null = $psCmd.BeginInvoke()  

Next, here's the modified clock script:

  1. param (  
  2.     [timespan]$period = (New-Object system.TimeSpan(0,5,0)),  
  3.     $clockxaml="<path to xaml file>\clock.xaml" 
  4. )  
  5.  
  6. ### Import the WPF assemblies  
  7. Add-Type -Assembly PresentationFramework  
  8. Add-Type -Assembly PresentationCore  
  9.  
  10. $clock = [Windows.Markup.XamlReader]::Load(   
  11.          (New-Object System.Xml.XmlNodeReader (  
  12.             [Xml](Get-Content $clockxaml) ) ) )  
  13.  
  14. $then = [datetime]::Now  
  15.  
  16. $red = [System.Windows.Media.Color]::FromRgb(255,0,0)  
  17. $redbrush = new-object system.windows.media.solidcolorbrush $red 
  18. $label = $clock.FindName("ClockLabel")  
  19. $done = $false 
  20.  
  21. # Create a script block which will update the UI  
  22. $updateBlock = {     
  23.    if (!$done) {  
  24.         # update the clock  
  25.         $elapsed = ([datetime]::Now - $then)  
  26.         $remaining = $null;  
  27.           
  28.         if ($elapsed -lt $period) {  
  29.             $remaining = ($period - $elapsed).ToString().substring(0,8)  
  30.         } else {  
  31.             $label.Foreground = $redbrush         
  32.             $remaining = "00:00:00" 
  33.             $done = $true 
  34.         }         
  35.         $clock.Resources["Time"] = $remaining 
  36.    }  
  37. }  
  38.  
  39. ## Hook up some event handlers   
  40. $clock.Add_SourceInitialized( {  
  41.    ## Before the window's even displayed ...  
  42.    ## We'll create a timer  
  43.    $timer = new-object System.Windows.Threading.DispatcherTimer  
  44.    ## Which will fire 2 times every second  
  45.    $timer.Interval = [TimeSpan]"0:0:0.50" 
  46.    ## And will invoke the $updateBlock  
  47.    $timer.Add_Tick( $updateBlock )  
  48.    ## Now start the timer running  
  49.    $timer.Start()  
  50.    if(! $timer.IsEnabled ) {  
  51.       $clock.Close()  
  52.    }  
  53. } )  
  54.  
  55. $clock.Add_MouseLeftButtonDown( {   
  56.    $_.Handled = $true 
  57.    $clock.DragMove() # WPF Magic!  
  58. } )  
  59.  
  60. $clock.Add_MouseRightButtonDown( {   
  61.    $_.Handled = $true 
  62.    $timer.Stop()  # we'd like to stop that timer now, thanks.  
  63.    $clock.Close() # and close the windows  
  64. } )  
  65.  
  66. ## Lets go ahead and invoke that update block   
  67. &$updateBlock 
  68. ## And then show the window  
  69. $clock.ShowDialog()  

...and finally the modified clock.xaml file:

  1. <Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  3.         xmlns:system="clr-namespace:System;assembly=mscorlib" 
  4.         WindowStyle='None' AllowsTransparency='True' 
  5.         Topmost='True' Background="Transparent"  ShowInTaskbar='False' 
  6.         SizeToContent='WidthAndHeight' WindowStartupLocation='CenterOwner' > 
  7.    <Window.Resources> 
  8.       <system:String x:Key="Time">12:34.56</system:String> 
  9.    </Window.Resources> 
  10.  
  11.    <Grid Height="2.2in"> 
  12.       <Grid.ColumnDefinitions> 
  13.          <ColumnDefinition/> 
  14.       </Grid.ColumnDefinitions> 
  15.       <Label Name="ClockLabel" Grid.Column="2" Opacity="0.7" Content="{DynamicResource Time}" FontFamily="Impact, Arial" FontWeight="800" FontSize="2in" > 
  16.          <Label.Foreground> 
  17.             <LinearGradientBrush> 
  18.                <GradientStop Color="#CC064A82" Offset="1"/> 
  19.                <GradientStop Color="#FF6797BF" Offset="0.8"/> 
  20.                <GradientStop Color="#FF6797BF" Offset="0.4"/> 
  21.                <GradientStop Color="#FFD4DBE1" Offset="0"/> 
  22.             </LinearGradientBrush> 
  23.          </Label.Foreground> 
  24.       </Label> 
  25.    </Grid> 
  26. </Window> 

Important: you'll need to save all files into the same directory and fix up the path to the clock.xaml file in the start-countdown.ps1 script.

Have fun!

posted on Friday, May 23, 2008 4:04:18 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Wednesday, May 21, 2008

This question has been asked in various ways over the last few years and I don't believe an answer that suits everyone has been proffered yet. I think this is part of a broader problem space that needs to be solved, one that I (and many others) have spent a bit thinking about -- for me personally, it's been mostly in the pub strangely enough, usually with a pint in hand -- and while I don't profess to have the answer, I do spent most of my powershell time tinkering with providers and have some views on this ;-)

Firstly, if you haven't seen this suggestion I've raised on connect (Allow providers other than the filesystem provider to surface commands) then take a gander at it now. It suggests allowing providers other than the FileSystemProvider to surface commands by using a new ProviderCapabilities flag. For those not able to read this suggestion, the bottom line is that currently one can execute a command on the filesystem by using the following syntax:

ps c:\> .\test.bat
hello, world

However, if you had another provider that linked into a mobile device, the amazon s3 service or MSL skydrive (when are they going to release an API?) for example, you would allow execution of commands with the same syntax, e.g.

ps skydrive:\> cd ppts
ps skydrive:\ppts> .\mydemo.ppt
The term '.\mydemo.ppt' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:8
+ .\mydemo.ppt <<<<

As you can see, this doesn't work.

What's important is having the same experience with similarly capable providers; e.g. those that can host executable content. Yes, you can implement support for invoke-item, but it's a bit discordant. One of the nicest features of powershell (and sometimes the most confusing) is that all providers - variable, function, environment, filesystem etc all hook into the same framework. There are some philosophically irksome differences like the fact that the  variable drive is the "default" provider, since dollar-qualified expressions are assumed to point there if not qualified with a drive name:

ps c:\> $host
Name             : ConsoleHost
Version          : 1.0.0.0
InstanceId       : 9d8a29bf-3d84-4ce6-8651-e0c72afb404b
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-CA
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy

so let's give a drive name this time:

ps c:\> ${c:test.bat}
@echo hello, world

This is a lot easier to understand once you realise that the '$' prefix is a grammatical shortcut into the IContentReader/IContentWriter interfaces on every provider; Much easier than just blindly committing to memory the method to read a variable and the method to read a file (imho). Once you introduce this capability into other providers, you then have to address ye olde $env:path variable. Currently this variable is imported from the system environment. The system, aka Windows, knows nothing about powershell and its drives. When using Get-Command, the search order for discovering commands is:

Aliases, Functions, Cmdlets, Scripts, Commands located in the directories specified by the Path environment variable, External scripts.

As we can see, Commands (5) are discovered via the $env:path variable. The other items (apart from 3 - cmdlets) all live in a flat namespace, so there's no path involved there.  I'd love if somehow it were possible to add any powershell path to this variable, even if they were limited to drive-qualified paths:

ps c:\> $env:path
c:\windows; c:\windows\system32; ...; s3:\utilities; "mobile:\storage card"; ...

Perhaps this information could be persisted inside PowerShell only so when the shell is exited, the path environment variable remains unchanged when viewed from the external windows system. This might mean that PowerShell paths would have to be appended in your profile at each load, but this isn't a bad thing either, IMO. So finally, on to the crux of the matter: disambiguation of identically named Cmdlets. Ultimately I don't believe there is a magic answer. This is solved in Windows by using the path variable, and so I believe it isn't such a bad idea to solve it with a path variable in powershell too. Behold $env:cmdletpath

ps c:\> $env:cmdletpath
Microsoft.PowerShell.Core; Microsoft.PowerShell.Host; Microsoft.PowerShell.Management; Microsoft.PowerShell.Security; Microsoft.PowerShell.Utility; VMWare.Commands.Utility

It's simple. It's optional. Snap-in qualified commands would continue to work, overriding the cmdlet search path. Instead of having to alias multiple commands when using a snapin that replaces a suite of built-in cmdlets, you can just re-jig the search path. Done. It's not the answer to everything, but it sure would make life a bit easier, no?

posted on Wednesday, May 21, 2008 7:36:39 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Wednesday, April 02, 2008

Now this is barely worth blogging, but one of the things I used a lot when I was confined to cmd.exe (yes, "confined" is the word I would use, 4NT aside) is the wonderfully simple copy con filename.txt then type a few lines and end it all with CTRL+Z and enter. So, if you're a PowerShell noob and yearn for this olden-days simplicity like dead parrots pine for the fjords, then this is for you:

  1. rm function:copy-console -ea 0 # silentlycontinue   
  2. rm alias:cc -ea 0   
  3.   
  4. # encoding can be String, Unicode, Byte, BigEndianUnicode, UTF8, UTF7, Ascii   
  5. function global:copy-console {   
  6.        
  7.     param(   
  8.         [string]$Filename = $(Throw "Need output filename."),   
  9.         $Encoding = "ASCII"  
  10.     )   
  11.        
  12.     $out = [io.path]::combine($pwd$Filename)   
  13.   
  14.     $buffer = @()   
  15.     $crlf = "`r`n"  
  16.        
  17.     do {   
  18.         $line = [console]::readline()   
  19.         if ($line -eq $null) { break; }   
  20.         $buffer += $line  
  21.     } while ($TRUE)   
  22.   
  23.     $buffer | set-content $out -Encoding $Encoding  
  24. }   
  25. new-alias cc copy-console   
  26.   
  27. # Usage:   
  28. #   
  29. # PS> cc test.txt -Encoding utf8   
  30. # bleh   
  31. # moop   
  32. # vlorg   
  33. # ^Z   
  34. # PS> cat test.txt   
  35. # bleh   
  36. # ...   
  37.   

I saved this to copy-console.ps1 and aliased it to "cc." Of course, you can do whatever you want - it's probably easier to just put it into a function in your profile. Just place the script above into your profile and remember: CTRL+Z then enter to save.

UPDATE 2008-04-04: Somehow I completely broke this in my attempts to "clean it up" before posting. I've reposted a better version (imho), and implemented encoding support as suggested out in Jason's comment below ;-)

posted on Wednesday, April 02, 2008 4:28:46 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
 Thursday, March 27, 2008

PowerShell has several "type accelerators" which are used exactly like a casting operation. Examples of these special operators are [xml] and [wmi]. The former is used for quickly converting a string of xml into a fully-fledged System.Xml.XmlDocument object.

Often I find myself converting things to and from hexadecimal using the -f operator, but this always seemed like just a little too much typing for me. Enter the [hex] accelerator type:

image

As you can see from the source below, there's no magic here. This is just a straight cast, but I have no namespace. If I had a namespace, say, like "Nivot.PowerShell", we'd have to cast using [nivot.powershell.hex] instead of just [hex]. All of the trickery is done using operator overloads in C#. These tells .NET (and in turn, powershell) how to behave should someone try to add, subtract, remove or divide our instances.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.  
  5. public class Hex  
  6. {  
  7.     private readonly int _value = 0;  
  8.  
  9.     private Hex(int value)  
  10.     {  
  11.         _value = value;  
  12.     }  
  13.  
  14.     private Hex(string value)  
  15.     {  
  16.         _value = Convert.ToInt32(value, 16);  
  17.     }  
  18.  
  19.     public static implicit operator Hex(int value)  
  20.     {  
  21.         return new Hex(value);  
  22.     }  
  23.  
  24.     public static implicit operator int(Hex value)  
  25.     {  
  26.         return value._value;  
  27.     }  
  28.  
  29.     public static explicit operator Hex(string value)  
  30.     {  
  31.         return new Hex(value);  
  32.     }  
  33.  
  34.     public static Hex operator +(Hex op1, Hex op2)  
  35.     {  
  36.         return new Hex(op1._value + op2._value);  
  37.     }  
  38.  
  39.     public static Hex operator -(Hex op1, Hex op2)  
  40.     {  
  41.         return new Hex(op1._value - op2._value);  
  42.     }  
  43.  
  44.     public static Hex operator *(Hex op1, Hex op2)  
  45.     {  
  46.         return new Hex(op1._value * op2._value);  
  47.     }  
  48.  
  49.     public static Hex operator /(Hex op1, Hex op2)  
  50.     {  
  51.         return new Hex(op1._value / op2._value);  
  52.     }  
  53.  
  54.     public override string ToString()  
  55.     {  
  56.         return "0x" + _value.ToString("X");
  57.     }  

The next step is to tell PowerShell's formatter what to do with the new type. Here's a simple format definition that tells the formatter to call ToString() on the Hex instance. This is the method that does the conversion by calling ToString("X") on the integer field. "X" means format the integer as hexadecimal using upper case. A lower-case "x" would output the value using lower-case (if you couldn't guess ;-)).

  1. <Configuration> 
  2.   <ViewDefinitions> 
  3.     <View> 
  4.       <Name>Hex</Name> 
  5.       <ViewSelectedBy> 
  6.         <TypeName>Hex</TypeName> 
  7.       </ViewSelectedBy> 
  8.       <CustomControl> 
  9.         <CustomEntries> 
  10.           <CustomEntry> 
  11.             <CustomItem> 
  12.               <ExpressionBinding> 
  13.                 <ScriptBlock>$_.ToString()</ScriptBlock> 
  14.               </ExpressionBinding> 
  15.             </CustomItem> 
  16.           </CustomEntry> 
  17.         </CustomEntries> 
  18.       </CustomControl> 
  19.     </View> 
  20.   </ViewDefinitions> 
  21. </Configuration> 

If we don't load this format file, PowerShell just emits a couple of blank lines when you try to use it.  You'll notice from the screenshot above that the blank lines still appear. I'm not sure how to remove these - it looks pretty ugly compared to the output of the [int] object. If you want to play with this, you can download the zip file below and unzip the contents into a single folder and run "hex.ps1". I didn't bother with a full Snap-In, it just loads the DLL using reflection. It also loads the format ps1xml too. Have fun.

HexAccelerator1.zip (2.4 KB)
posted on Thursday, March 27, 2008 8:19:15 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Thursday, March 06, 2008

Just a minor thing, but this is a filter I've been using for a while now as a replacement for Get-Member. It's always annoyed me that the Get_ and Set_ methods are returned along with the actual properties. The only place that's actual useful is when you're using an object wrapped in the [xml] adapter since those objects do not expose the XmlDocument's properties in the adapted member set.

update: if you use (of course you do!) MoW's PowerTab, you can disable display of the accessor methods with $PowerTabConfig.ShowAccessorMethods = $false.

  1. filter get-memberex {   
  2.     $_ | gm | ? { -not($_.name -match "^[gs]et_.+") }   
  3. }   
  4. new-alias gmx get-memberex  
posted on Thursday, March 06, 2008 11:35:21 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Friday, February 29, 2008
Someone on the PowerShell usenet group asked if it was possible to interact with SharePoint lists through our favourite little shell. Marco Shaw responded and put the pressure on by saying this was my bag of tricks. Who am I to say otherwise? so lets take a look at the recipe.
posted on Friday, February 29, 2008 10:11:39 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Thursday, February 14, 2008

My last post got me thinking about the problems experienced when trying to write culture aware software. Yeah, I know it was actually me that was unaware of the culture, but this time it's about the software end of the deal; in particular, the recently updated Microsoft Business Data Catalog Definition Editor for Microsoft's popular SharePoint 2007 server. If you read some of the comments on the blog, you'll see that various people (using a non US English version of Windows) have installed it and have come across a problem where the tool cannot find the local security group called "Builtin\Users." Oops. In the world of cutting-edge technology, people often install software that doesn't match the installed language of their O/S. The fact of the world is that all major symbolic computer languages are based around English, and the most popular software gets written in English first. Here in Quebec, Canada, French is the primary language with English coming second (Canada is officially bilingual - although most of the country only speaks English). Localization of software takes a fair amount of time. It's not just translating a resources file - there are hot-keys to reassign (the Bold shotcut in French MSWord is CTRL+G for example, bold being Gras in French) dialog boxes to resize, labels and controls to reposition etc. Some languages are more verbose than others and end up with text that won't fit. However, there are things you do to avoid certain problems -- lets take the issue above as an example.

Logins and Group names are just an abstraction in the Windows security subsystem. These things are actually represented by value called a SID ( system.security.principal.securityidentifier ). No matter what version of Windows you use, the SIDs for built-in accounts and groups are the same:

First using an en-US system:

  1. PS > $acc = new-object System.Security.Principal.NTAccount "Users" 
  2. PS > $acc.Translate( [System.Security.Principal.SecurityIdentifier] ).value  
  3. S-1-5-32-545 

and a French (fr-FR) system:

  1. PS > $acc = new-object System.Security.Principal.NTAccount "Utilisateurs" 
  2. PS > $acc.Translate( [System.Security.Principal.SecurityIdentifier] ).value  
  3. S-1-5-32-545 

As you can see, the SID is the same: S-1-5-32-545. An example of this is shown below - a simple If-Elevated function that takes two Scriptblocks: the first is executed if the user is running as an administrator, the second is running if the user is just a plain well, user:

  1. # Usage:  
  2. #  
  3. # If-Elevated { .. admin code .. } { "sorry, need admin" }   
  4. #  
  5.  
  6. function If-Elevated {  
  7.   param(  
  8.     [scriptblock]$AsAdmin = $(Throw "Missing 'as admin' script"),  
  9.     [scriptblock]$AsUser= $(Throw "Missing 'as user' script")  
  10.   )  
  11.    
  12.   $identity = [security.principal.windowsidentity]::Getcurrent()  
  13.   $principal = new-object  security.principal.windowsprincipal $identity 
  14.   $adminsRole =  [system.security.principal.securityidentifier]"S-1-5-32-544" 
  15.                   
  16.   if ($principal.IsInRole($adminsRole)) {  
  17.     & $AsAdmin 
  18.   } else {  
  19.     & $AsUser 
  20.   }  
  21. }  
So ok, it doesn't have localized messages, but at least it will execute correctly on other locales ;-) Have fun.
posted on Thursday, February 14, 2008 6:36:06 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Monday, January 14, 2008

I'm happy to say that the powers that be in Microsoft have deemed me MVP worthy - I am now an official Microsoft Most Valued Professional in Windows Server Admin Frameworks for 2008, more specifically for my open source work and public support of Microsoft's most excellent object-oriented interactive shell, PowerShell during the last 18 months.

MVP_Horizontal_BlueOnly

I've just returned from an extended computational absence, so hopefully I can get back to churning out code and the odd blog post.

posted on Monday, January 14, 2008 10:53:30 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
 Friday, December 14, 2007

In case anyone is interested, here are my slides in PowerPoint PPTX format from the most recent PS Virtual User Group. It covers the new Path handling infrastructure for PscxCmdlets in the upcoming PowerShell Community Extensions 1.2 and some brief information on my PowerShell Eventing snap-in for PowerShell.

psvug2_oisin_grehan.zip (152 KB)

(updated to a zip: it appears DasBlog will not serve pptx files?)

posted on Friday, December 14, 2007 11:04:07 AM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
 Friday, December 07, 2007

As knowledge of PowerShell increases for those new to .NET, there comes a point when people start to notice some shortcomings of the Assembly loading/unloading mechanisms of the 2.0 CLR. Namely, once you load an assembly into PowerShell to use it, you can't unload it again. The only way to remove it from memory is to restart PowerShell. Eventually, you might read something about how Assemblies can be loaded into AppDomains, and AppDomains themselves can be unloaded. This is true, but for the most part it is not much use in PowerShell unless the Types in question where specifically designed with this in mind. For those of you who understand enough of what I'm talking about to get this far without going "huh?", the following script will demonstrate some of the issues at hand:

Before you run this script, please disable PowerTab or any other SnapIns that may load the WinForms assembly into the current AppDomain. In short, this script creates a Form object in a child AppDomain, examines the current AppDomain for the WinForms assembly. It then attempts to manipulate the Form and again examines the current AppDomain for the WinForms assembly.

  1. # full qualified display name to WinForms assembly   
  2. $assembly = "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  
  3.   
  4. function IsWinFormsLoaded() {   
  5.     $loaded = [appdomain]::currentdomain.getassemblies()   
  6.     $winforms = $loaded | ? { $_.fullname -like "system.windows*" }   
  7.     return ($winforms -ne $null)       
  8. }   
  9.   
  10. if (-not (IsWinFormsLoaded)) {   
  11.     "Creating child AppDomain..."  
  12.     $child = [appdomain]::Createdomain("child",$null,$null)   
  13.   
  14.     # create a remote instance of a WinForms Form in a c