The following can be easily added to your PowerShell profile or imported as a module to create convenient variables for referencing special folder paths. For example, “$PathDesktop\blah.txt”.

##############################################################################
#.SYNOPSIS
# Gets the path to the system special folder identified by the folder name.
#
#.DESCRIPTION
# This function is equivalent to the Environment.GetFolderPath method.
#
#.PARAMETER Folder
# An Environment.SpecialFolder enumeration value which can be specified as
# a string or literal in PowerShell (see example.)
#
#.EXAMPLE
# Get-SpecialFolder Favorites | Set-Location
#
#.RETURNVALUE 
# The full path to the specified system folder.
##############################################################################
function Get-SpecialFolder([Environment+SpecialFolder]$Folder) {
    [Environment]::GetFolderPath($Folder)
}

function Script:Set-SpecialFolderVariable([Environment+SpecialFolder]$Folder,[String]$Description) {
    Set-Variable "Path$Folder" (Get-SpecialFolder $Folder) -Scope Global -Option AllScope,ReadOnly -Description $Description -Force
}

Set-SpecialFolderVariable ApplicationData        -Description "The directory that serves as a common repository for application-specific data for the current roaming user."
Set-SpecialFolderVariable LocalApplicationData   -Description "The directory that serves as a common repository for application-specific data that is used by the current, non-roaming user."
Set-SpecialFolderVariable Desktop                -Description "The logical Desktop rather than the physical file system location."
Set-SpecialFolderVariable DesktopDirectory       -Description "The directory used to physically store file objects on the desktop."
Set-SpecialFolderVariable Personal               -Description "The directory that serves as a common repository for documents."
Set-SpecialFolderVariable MyComputer             -Description "The `"My Computer`" folder."
Set-SpecialFolderVariable MyMusic                -Description "The `"My Music`" folder."
Set-SpecialFolderVariable MyPictures             -Description "The `"My Pictures`" folder."
Set-SpecialFolderVariable Favorites              -Description "The directory that serves as a common repository for the user's favorite items."
Set-SpecialFolderVariable Recent                 -Description "The directory that contains the user's most recently used documents."
Set-SpecialFolderVariable SendTo                 -Description "The directory that contains the Send To menu items."
Set-SpecialFolderVariable Templates              -Description "The directory that serves as a common repository for document templates."
Set-SpecialFolderVariable StartMenu              -Description "The directory that contains the Start menu items."
Set-SpecialFolderVariable Programs               -Description "The directory that contains the user's program groups."
Set-SpecialFolderVariable Startup                -Description "The directory that corresponds to the user's Startup program group."
Set-SpecialFolderVariable InternetCache          -Description "The directory that serves as a common repository for temporary Internet files."
Set-SpecialFolderVariable Cookies                -Description "The directory that serves as a common repository for Internet cookies."
Set-SpecialFolderVariable History                -Description "The directory that serves as a common repository for Internet history items."
Set-SpecialFolderVariable ProgramFiles           -Description "The program files directory."
Set-SpecialFolderVariable System                 -Description "The System directory."
Set-SpecialFolderVariable CommonApplicationData  -Description "The directory that serves as a common repository for application-specific data that is used by all users."
Set-SpecialFolderVariable CommonProgramFiles     -Description "The directory for components that are shared across applications."

If you’re a developer or system administrator, I would highly suggest picking up PowerShell In Action by Bruce Payette or some other book on PowerShell and adding this important skill to your resume. I frequent the PowerShell newsgroups and I can say without any hesitation that the activity is growing quickly. If you manage a SQL or Exchange server and in the near future Active Directory, OCS, or pretty much any product that ends with the word “server” you will need to learn PowerShell or risk becoming a dinosaur.

This has been a public service announcement by Josh Einstein.

It’s the little things in PowerShell that make a rigid C# developer smile… In the following example, I distribute an array result to separate variables in one line.

$NPA,$NXX,$Digits = Split-Phone 202-456-1111

# In C#, the equivalent would be
# string[] results = SplitPhone("202-456-1111");
# string npa = results.Length > 0 ? results[0] : null;
# string nxx = results.Length > 1 ? results[1] : null;
# string digits = results.Length > 2 ? results[2] : null;

And even the C# syntax mentioned above doesn’t exactly do the same thing because in PowerShell, if the array contains more elements than you’re distributing, the last variable can receive the “remainder” of the array. Since C# doesn’t really have a concept of singleton arrays being treated as strings, there is no direct analogy.

##############################################################################
#.SYNOPSIS
# Splits a phone number into NPA, NXX, and 4 digit components.
#
#.PARAMETER TN
# The telephone number to split.
#
#.RETURNVALUE
# If the specified number is a valid US telephone number, the return value is
# a 3 element array containing NPA, NXX, and the 4 digit line. Otherwise, an
# exception is thrown.
##############################################################################
function Split-Phone {

    param ( [String]$TN )

    
    $Pad = '\s*'
    $Sep = '(\s+|\.|\-)?'
    $Intl = '(?<intl>\+?1)?'
    $NPA = '(\s*\(?\s*(?<npa>\d{3})\s*\)?\s*)'
    $NXX = '(?<nxx>\d{3})'
    $Digits = '(?<digits>\d{4})?'
    
    $Pattern = "^$Pad$Intl$Sep$NPA$Sep$NXX$Sep$Digits$Pad`$"
    
    Write-Debug "Split-Phone Pattern: $Pattern"
    
    if ( $TN -match $Pattern ) {
        if ( $Matches['digits'] ) {
            @(  $Matches['npa'],
                $Matches['nxx'],
                $Matches['digits'] )
        }
        else {
            @(  $Matches['npa'],
                $Matches['nxx'] )
        }
    }
    else {
        throw "Invalid TN: $TN" 
    }
       
}
Mar 012009

Live Mesh is a great piece of software. But you may or may not be aware that it doesn’t synchronize all files. There’s various reasons why it might exclude a particular file, which are detailed here.

If you’re like me and spend a lot of time evaluating beta software and operating systems, juggle multiple machines, etc. you may be tempted to blow away your hard drive’s contents assuming that Live Mesh has you covered. In most cases you’ll be fine but for a sanity check, here’s a PowerShell script that will show you which files in a particular folder will not be synchronized due to Mesh’s exclusion criteria.

Note: This script requires PowerShell 2.0 CTP 3 as all of my scripts do.

Note: This will only tell you which files meet the exclusion criteria and does not guarantee that files not reported are actually synchronized.

##############################################################################
#.SYNOPSIS
# Lists the files in a directory that are excluded from live mesh synchronization.
#
#.PARAMETER Path
# Specifies the path to an item. Wildcards are permitted.
#
#.PARAMETER LiteralPath
# Specifies the path to an item. Unlike Path, the value of LiteralPath is
# used exactly as it is typed. No characters are interpreted as wildcards.
# If the path includes escape characters, enclose it in single quotation marks.
# Single quotation marks tell Windows PowerShell not to interpret any
# characters as escape sequences.
#
#.PARAMETER ShowProgress
# If specified, a progress indicator will show the files being processed.
#
#.EXAMPLE
# Get-MeshExclusions C:\Personal
##############################################################################
function Get-MeshExclusions {

[CmdletBinding(DefaultParameterSetName='Path')]
param(

    [Parameter(ParameterSetName='Path', Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String[]]$Path='.',
    
    [Alias("PSPath")]
    [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
    [String[]]$LiteralPath,
    
    [Parameter()]
    [Switch]$ShowProgress
    
)

begin {

    $ExcludedExtensions = @('.bak','.gfs','.laccdb','.lnk','.mbf','.mny','.mpc','.pst','.sav','.tmp','.wlx')
    $ExcludedNames = @('desktop.ini','thumbs.db')
    $ExcludedFolders = @([Environment]::GetFolderPath('DesktopDirectory'))


}

process {

    switch ($PSCmdlet.ParameterSetName) {
        Path            { $ResolveArgs = @{Path=$Path} }
        LiteralPath     { $ResolveArgs = @{LiteralPath=$LiteralPath} }
    }

    if ($ShowProgress) { $PathResolver = { Resolve-Path @ResolveArgs | Progress-Object } }
    else { $PathResolver = { Resolve-Path @ResolveArgs } }
    
    &$PathResolver | %{

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand.Name) Processing $_"

        #################
        # PUT CODE HERE #
        #################
        
        Get-ChildItem -Path $_ -Recurse -Force | %{

            $Exclusion = 'Included'

            if ($ExcludedExtensions -contains $_.Extension) {
                $Exclusion = 'Blocked Extension'
            }
            elseif ($ExcludedNames -contains $_.Name) {
                $Exclusion = 'Special File'
            }
            elseif ($_.Name -like '~*') {
                $Exclusion = 'Tilde'
            }
            elseif ($ExcludedFolders -contains $_.DirectoryName) {
                $Exclusion = 'Blocked Directory'
            }
            elseif ($_.Attributes -band [IO.FileAttributes]::Hidden) {
                $Exclusion = 'Hidden File'
            }
            elseif ($_.Attributes -band [IO.FileAttributes]::System) {
                $Exclusion = 'System File'
            }
            
            if ($Exclusion -ne 'Included') {
                $_ | Add-Member NoteProperty Exclusion $Exclusion -PassThru
            }
        
        } | Select Name,DirectoryName,Length,Exclusion

    }

}

}

http://richardsiddaway.spaces.live.com/blog/cns!43CFA46A74CF3E96!2089.entry

A couple things come to mind:

  1. Why aren’t they just referencing the same ParameterAttribute the way C# does?
  2. Specifying System.Management.Automation.ParameterAttribute doesn’t work either. It causes the error message to complain about not being able to find an attribute class that very much does exist.
  3. Are they planning on supporting .NET attributes or maybe even classes in the future? (The PowerShell purist in me says “no no”, but the C# guy in me says “yes yes”!)
  4. Shouldn’t it raise the error at the time the function is defined and not when it is invoked?
function Blah {
   [CmdletBinding()]
   param(
      [System.Management.Automation.ParameterAttribute (Mandatory=$true)]
      [String] $Name
    )
    
    "Hello $Name"
    
}

Blah "Jerry"
Blah "Newman"

Update: I should have mentioned this is specific to PowerShell 2.0 CTP 3 and may not work this way when it is released.

As far as I can tell, this is undocumented. But with a little help from Reflector and Get-Command, I stumbled upon a parameter to the Import-Module command called ArgumentList. It seems this lets you pass parameters to the module in your Import-Module call and they show up in the $Args special variable.

This is pretty useful if I have a module that is otherwise reusable except for some "Josh-specific" stuff that wouldn’t work on someone else’s machine. For example, I have a module that wraps up a bunch of SharePoint web services functionality. But it’s kind of a pain to have to specify the same URL over and over again.

So in my profile.ps1, this is how I am loading the module:

Import-Module Telecommunications
Import-Module BAF
Import-Module SNMP
Import-Module MetaSwitch
Import-Module LERG
Import-Module VMWare

Import-Module SharePointWS -ArgumentList 'http://sharepoint.lsi.local'

And at the top of my SharePointWS.psm1, this is how I am accessing the argument.

if ($Args.Length) {
    $Script:DefaultSharePointUrl = $Args[0]
}

I hate to admit it, but the convenience of having PowerShell ISE installed with the core installation of PowerShell 2.0 has been enough to get me to switch from PowerGUI, which is in my opinion a much more full featured free editor/debugger.

One thing that I sorely missed from PowerGUI (which is compounded by the fact that unless you’re on Windows 7, PowerShell ISE doesn’t have a "Recent Files" menu) is the ability to quickly open my profile script without having to browse for it.

Well PowerShell ISE is extensible, so if it lacks a feature, you can just add it in most cases. I’ve uploaded my PowerShell ISE profile which has some useful functions for automating the ISE. But the one in particular that implements the "Open Profile" feature is called….(wait for it)…. Open-Profile!

So you can type that in the immediate window, or use the custom menu item that gets added. It opens the user/global/host/generic/etc profiles if they exist.

function Open-File([String]$Path,[Switch]$Quiet) {

    $AllowedExtensions = @('.ps1','.psm1','.psd1','.ps1xml','.txt','.txt')
    $Extension = [System.IO.Path]::GetExtension($Path)

    try {

        if ( -not ($AllowedExtensions -contains $Extension) ) {
            throw "OpenFile: $Path cannot be opened in PowerShell ISE."
        }
        if ( -not (Test-Path $Path) ) {
            throw "OpenFile: $Path does not exist."
        }

        $Path = (Resolve-Path $Path).ProviderPath
        $PSISE.CurrentOpenedRunspace.OpenedFiles.Add($Path)

    }
    catch {
        if ( -not $Quiet ) { Write-Warning $_ }
    }

}

function Open-Profile {
    Open-File $PROFILE.CurrentUserAllHosts -Quiet
    Open-File $PROFILE.CurrentUserCurrentHost -Quiet
    Open-File $PROFILE.AllUsersAllHosts -Quiet
    Open-File $PROFILE.AllUsersCurrentHost -Quiet
}

Everybody has their own idea of what the perfect prompt in PowerShell consists of. My prompt is pretty boring, but it’s got one pretty unique feature – my prompt function doubles as a useful command!

You see, a lot of times I wind up in deep directories of solution and project folders in Visual Studio and I wind up with a prompt that barely fits on one line, or even worse, wraps. In this cases, I often only care about having a vague idea of where I am.

# sometimes I only want a little information about where I am.
C:\Users\Josh Einstein\Documents\Visual Studio\Projects> Prompt -Minimize

# sometimes I don't care at all
PS Projects> Prompt -Hide

# if I need to bring it back to the way it was, I can just do this.
PS> Prompt -Show
C:\Users\Josh Einstein\Documents\Visual Studio\Projects>

This has really come in handy for me and it’s something you could easily do with a separate script or function in your profile. But I figured, hey the prompt function is already there right? What’s it gonna hurt if I add a few parameters to it?

##############################################################################
#.SYNOPSIS
# The all-important Prompt function called by PowerShell in order to display
# the prompt, with an added twist using the built-in parameters.
#
#.DESCRIPTION
# Customizing the prompt is common in PowerShell, but this function adds some
# parameters to the function that enable some dynamic behavior. These
# parameters will not affect the way PowerShell calls the function, but they
# allow you to minimize or hide the prompt by passing switches.
# 
#.PARAMETER Minimize
# Shrinks the prompt so that it shows only the leaf portion of the current
# provider path. Restore it using the -Show parameter.
#
#.PARAMETER Hide
# Sets the prompt to an ultra-compact mode, displaying only an indicator,
# and no information about the current location.
#
#.PARAMETER Show
# Restores the prompt to it's default mode which displays the full location
# of the current provider path.
#
#.LINK
# about_prompts
#
#.EXAMPLE
# Prompt -Minimize
# Prompt -Hide
# Prompt -Show
##############################################################################
function Prompt {

    [CmdletBinding(DefaultParameterSetName='Prompt')]
    param ( 

        [Parameter(ParameterSetName='Minimize')]
        [Switch] $Minimize,

        [Parameter(ParameterSetName='Hide')]
        [Switch] $Hide,

        [Parameter(ParameterSetName='Show')]
        [Switch] $Show

    )

    switch ( $PSCmdlet.ParameterSetName ) {
        Minimize { $Global:PromptVisibility = 'Minimized' }
        Hide     { $Global:PromptVisibility = 'Hidden' }
        Show     { $Global:PromptVisibility = 'Default' }
        Prompt   {
            switch ( $PromptVisibility ) {
                Minimized { "PS $(Split-Path $PWD -Leaf)> " }
                Hidden    { "PS> " }
                Default   { "$PWD> " }
            }
        }
    }

}

One of the things I really like about PowerShell is how through the use of script blocks and locally scoped functions, you can create interesting script using perfectly valid, albeit odd looking PowerShell script. I had first done something like this without script blocks using the pipeline which was pretty ugly. It was mostly just a way of quickly building up objects using functions that wrapped New-Object and Add-Member.

New-PSObject |
    Field FirstName 'Josh' |
    Field LastName 'Einstein'

That was pretty handy but as a C# guy it still looked ugly. I got the idea from Bruce Payette’s book, PowerShell in Action to use script blocks and locally-scoped functions so that I didn’t have to pollute the runspace with a bunch of functions that were only valid within the context of another function.

New-PSObject {

    Field FirstName 'Josh'
    Field LastName 'Einstein'

    Property FullName { "$($this.FirstName) $($this.LastName)" }

    Method SayHello {
        param($to)
        "$($this.FirstName) says 'Hello' to $to!"
    }

}

How does it work? Well, like most things in PowerShell, functions have scope. So a function defined within another function is only valid during the execution of the outer function. This makes it possible to trample on existing functions and cmdlets too.

In Bruce Payette’s book he calls them "little languages".

This technique allows for some really clean and concise syntax similar to how some DSL’s are being used in .NET.

# Global functions used by New-PSObject
function Field([String]$Name,$Value,[String]$Format) {
    @{ MemberType='NoteProperty'; Name=$Name; Value=$Value; Format=$Format }
}
function Property([String]$Name,[ScriptBlock]$GetDefinition,[ScriptBlock]$SetDefinition) {
    @{ MemberType='ScriptProperty'; Name=$Name; Value=$GetDefinition; SecondValue=$SetDefinition }
}
function Method([String]$Name,[ScriptBlock]$MethodDefinition) {
    @{ MemberType='ScriptMethod'; Name=$Name; Value=$MethodDefinition }
}

##############################################################################
#.SYNOPSIS
# Creates a PSObject and initializes it with fields, properties, and methods
# using a ScriptBlock written in a mini-language.
#
#.DESCRIPTION
# The ScriptBlock passed to the Definition parameter can contain commands
# that are private to this function, such as Field, Property, and Method
# which define members on the new PSObject.
#
#.PARAMETER Definition
# The ScriptBlock that builds up the PSObject by using special commands such
# as Field, Property, and Method. See the example for more information.
#
#.PARAMETER InputObject
# A HashTable that contains key/value pairs that will be turned into
# NoteProperties on the new PSObject.
#
#.EXAMPLE
# New-PSObject {
#     Field FirstName 'Josh'
#     Field LastName 'Einstein'
#     Property FullName { "$($this.FirstName) $($this.LastName)" }
#     Method GetFullName { "$($this.FirstName) $($this.LastName)" } 
# }
#
#.EXAMPLE
# @{FirstName='Josh';LastName='Einstein'} | New-PSObject
##############################################################################
function New-PSObject {

    [CmdletBinding(DefaultParameterSetName='Definition')]
    param ( 

        [Parameter(ParameterSetName='Definition', Position=1)]
        [ScriptBlock]$Definition,
        
        [Parameter(ParameterSetName='Hashtable', Mandatory=$true, ValueFromPipeline=$true)]
        [HashTable]$InputObject
        
    )

    begin { }
    
    process {

        $Object = New-Object PSObject
        
        if ( $InputObject ) {        
            foreach ( $k in $InputObject.Keys ) {
                Add-Member -InputObject $Object -MemberType NoteProperty -Name $k -Value $InputObject[$k]
            }
        }
        elseif ( $Definition ) {

            $Members = &$Definition
            
            foreach ( $Member in $Members ) {
                if ( $Member.Format ) { $Member.Value = [String]::Format([String]$Member.Format,[String]$Member.Value) }
                if ( $Member.ContainsKey('SecondValue') ) { 
                    Add-Member -InputObject $Object `
                                -MemberType $Member.MemberType `
                                -Name $Member.Name `
                                -Value $Member.Value `
                                -SecondValue $Member.SecondValue
                }
                else {
                    Add-Member -InputObject $Object `
                                -MemberType $Member.MemberType `
                                -Name $Member.Name `
                                -Value $Member.Value
                }
            }

        }
        
        $Object
        
    }
    
    end { }

}
function ConvertTo-Dictionary {
  param(
    [ScriptBlock]$KeyExpression,
    [ScriptBlock]$ValueExpression={$_}
  )
  begin { $Result = @{} }
  process { $Result[(&$KeyExpression)]=(&$ValueExpression) }
  end { $Result }
}

I already have this function in a compiled C# PSSnapin that I’ve been using (it’s named differently, which would piss off the PowerShell verb nazi’s) but here is a very short and useful function that you can use to turn any pipeline into a dictionary using an expression for extracting the key and value. Very similar to LINQ’s ToDictionary extension method – not coincidentally.

$Services = Get-Service | ConvertTo-Dictionary {$_.Name} {$_.DisplayName}
$Services['WSearch']
# Windows Search