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."

I’m pretty excited about this new module. Originally I threw something very crappy together just to get a rough idea of which commands I was calling. But then I started polishing it up and polishing it up and I arrived at something I just had to post.

Dir . -i *.ps1,*.psm1 -r | Get-Dependency -Unresolved | Out-GridView

The above line of code will scan each ps1 and psm1 file in the current directory as well as subdirectories and send the information about any unresolved dependencies to the Out-GridView cmdlet for display. Another nice feature is that if you run the command from within PowerShell ISE and you don’t give it a path, it will check whichever file is currently open in the editor.

NAME
    Get-Dependency

SYNOPSIS
    Calculates the dependencies of a script file, block of PowerShell code, or
    an open PowerShell ISE document.

SYNTAX
    Get-Dependency [-Unresolved] [-Force] [-Verbose] [-Debug] [-ErrorAction []]
                   [-WarningAction []] [-ErrorVariable []]
                   [-WarningVariable []] [-OutVariable []] [-OutBuffer []]
                   []

    Get-Dependency [-Path] [] [-Unresolved] [-Force] [-Verbose] [-Debug]
                   [-ErrorAction []] [-WarningAction []]
                   [-ErrorVariable []] [-WarningVariable []] [-OutVariable []]
                   [-OutBuffer []] []

    Get-Dependency -LiteralPath [] [-Unresolved] [-Force] [-Verbose] [-Debug]
                   [-ErrorAction []] [-WarningAction
                   []] [-ErrorVariable []] [-WarningVariable []]
                   [-OutVariable []] [-OutBuffer []] []

DETAILED DESCRIPTION
    Before deploying a script or module, it is important to ensure that any
    external dependencies are resolved otherwise code that runs fine on your
    machine will bomb on someone else's. This function will scan a single
    level of dependencies from the specified script and by default returns
    any dependency that is 1) not a part of the built-in PowerShell command
    and variable configuration and 2) not defined in the script being analyzed.
    You can override this behavior and include these dependencies with the
    Force parameter.

PARAMETERS
    -Path
        Specifies the path to an item. Wildcards are permitted.

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?  

    -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.

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?  

    -Unresolved
        When specified, only unresolved dependencies are returned.

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?  

    -Force
        When specified, all dependencies are included, even if they are known to
        be defined locally or in the default PowerShell configuration.

        Required?                    false
        Position?                    named
        Default value
        Accept pipeline input?       false
        Accept wildcard characters?  

        This cmdlet supports the common parameters: -Verbose, -Debug,
        -ErrorAction, -ErrorVariable, -WarningAction, -WarningVariable,
        -OutBuffer and -OutVariable. For more information, type,
        "get-help about_commonparameters".

RETURN TYPE
    An array of PSObjects containing information about the external dependencies.

    -------------------------- EXAMPLE 1 --------------------------

    Get-Dependency | Out-GridView

RELATED LINKS
    Get-PSToken

imageI will post back with some more information later, but the basic idea is that this uses the new PSParser class to parse a PowerShell script into tokens. It then analyzes the tokens to figure out some basic facts about the script:

  • Which global variables are being referenced
  • Which modules does the script import that are not currently imported
  • Which commands are being referenced by the script?
    • Which commands are functions defined in the current script?
    • Which command are aliases and which commands do they resolve to?
    • Which commands are built-in functions/cmdlets/aliases/etc?

Once all of the above has been determined, it’s presented in a format that is easy to read.

I added mine to the custom menu of PowerShell ISE. :)

Dependency Module

I don’t know about you but I am sick and tired of the choice I have to make between pretty formatting of values and usability. This applies to pretty much all .NET technology and I assume most languages in general. What’s my problem? Well let’s say you list the size of all files by extension in a directory.

Dir . -Recurse | 
Group Extension | %{ 
    $_ | 
    Select Name,@{
        N='Size';
        E={($_.Group | Measure-Object Length -Sum).Sum }
    }
}

The nice thing is, you can pipe this out to Sort-Object to sort by length. The downside is, it looks like crap. It would be nice if it showed MB, KB, bytes, etc. But once you format as a string, you can no longer easily filter or sort the output.

So please, reap the benefits of my frustration and use the following C# class with Add-Type to cast your data size formatting problems into oblivion.

Add-Type -ReferencedAssemblies System.Xml -TypeDefinition @"
   ... the code that stupid Windows Live says is too long to post ...
"@

Dir . -Recurse | 
Group Extension | %{ 
    $_ | 
    Select Name,@{
        N='Size';
        E={[DataSize]($_.Group | Measure-Object Length -Sum).Sum }
    }
}

Source is below.

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace Einstein
{

    /// <summary>
    /// Represents a file size or other data size in bytes, kilobytes, megabytes, etc.
    /// </summary>
    [Serializable]
    [ImmutableObject( true )]
    public struct DataSize : IFormattable, IComparable<DataSize>, IComparable, IConvertible, IEquatable<DataSize>, IXmlSerializable
    {

        #region Constants

        /// <summary>
        /// The size of a byte, in bytes. Always 1, provided for consistency only.
        /// </summary>
        public const long ByteSize = 1;

        /// <summary>
        /// The size of a kilobyte, in bytes. This structure defines a KB as 1,024 bytes.
        /// </summary>
        public const long KilobyteSize = 1024;

        /// <summary>
        /// The size of a megabyte, in bytes. This structure defines a MB as 1,024^2 or 1,048,576 bytes.
        /// </summary>
        public const long MegabyteSize = 1048576;

        /// <summary>
        /// The size of a gigabyte, in bytes. This structure defines a GB as 1,024^3 or 1,073,741,824 bytes.
        /// </summary>
        public const long GigabyteSize = 1073741824;

        /// <summary>
        /// The size of a terabyte, in bytes. This structure defines a TB as 1,024^4 or 1,099,511,627,776 bytes.
        /// </summary>
        public const long TerabyteSize = 1099511627776;

        /// <summary>
        /// The suffix appended to the end of a string represented as bytes.
        /// </summary>
        public const string ByteSuffix = "B";

        /// <summary>
        /// The suffix appended to the end of a string represented as kilobytes.
        /// </summary>
        public const string KilobyteSuffix = "KB";

        /// <summary>
        /// The suffix appended to the end of a string represented as megabytes.
        /// </summary>
        public const string MegabyteSuffix = "MB";

        /// <summary>
        /// The suffix appended to the end of a string represented as gigabytes.
        /// </summary>
        public const string GigabyteSuffix = "GB";

        /// <summary>
        /// The suffix appended to the end of a string represented as terabytes.
        /// </summary>
        public const string TerabyteSuffix = "TB";

        #endregion

        #region Fields

        /// <summary>
        /// Holds the value of the data size, in bytes.
        /// </summary>
        private long bytes;

        /// <summary>
        /// Regular expression used to pick apart the format string.
        /// </summary>
        private static readonly Regex formatRegex = new Regex( @"(?<unit>A|B|K|M|G|T)(?<precision>\d+)?(?<nosuffix>\*)?", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Singleline );

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new DataSize representing the specified number of bytes.
        /// </summary>
        public DataSize( long bytes )
        {
            this.bytes = bytes;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the value in terabytes.
        /// </summary>
        public decimal TotalTerabytes
        {
            get
            {
                return bytes / (decimal)TerabyteSize;
            }
        }

        /// <summary>
        /// Gets the value in gigabytes.
        /// </summary>
        public decimal TotalGigabytes
        {
            get
            {
                return bytes / (decimal)GigabyteSize;
            }
        }

        /// <summary>
        /// Gets the value in megabytes.
        /// </summary>
        public decimal TotalMegabytes
        {
            get
            {
                return bytes / (decimal)MegabyteSize;
            }
        }

        /// <summary>
        /// Gets the value in kilobytes.
        /// </summary>
        public decimal TotalKilobytes
        {
            get
            {
                return bytes / (decimal)KilobyteSize;
            }
        }

        /// <summary>
        /// Gets the value in bytes.
        /// </summary>
        public decimal TotalBytes
        {
            get
            {
                return (decimal)bytes;
            }
        }

        /// <summary>
        /// Gets the value in bytes as a signed 64 bit integer.
        /// </summary>
        public long Bytes
        {
            get
            {
                return bytes;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Converts the String representation of a number to its DataSize equivalent. A return value indicates whether the conversion succeeded or failed. 
        /// </summary>
        /// <remarks>
        /// <para>
        /// The string can take on the format of:
        /// <list type="bullet">
        ///     <item>128 MB</item>
        ///     <item>40.00 KB</item>
        ///     <item>78 gigabytes</item>
        ///     <item>1 terabyte</item>
        /// </list>
        /// </para>
        /// <para>
        /// Lowercase representations will be accepted such as a b, kb, mb, etc but they will not be treated as "bits" they
        /// will be treated as "bytes". Their acceptance is simply to keep the function case insensitive.
        /// </para>
        /// </remarks>
        /// <param name="input">A String object containing a data size to convert.</param>
        /// <returns>A DataSize structure representing the specified string.</returns>
        public static DataSize Parse( string input )
        {

            if ( input == null )
                throw new ArgumentNullException( "input" );

            DataSize dataSize;
            if ( TryParse( input, out dataSize ) )
                return dataSize;
            else
                throw new FormatException( "Could not parse the specified string into a DataSize." );

        }

        /// <summary>
        /// Converts the String representation of a number to its DataSize equivalent. A return value indicates whether the conversion succeeded or failed.
        /// </summary>
        /// <param name="input">A String object containing a number to convert.</param>
        /// <param name="dataSize">When this method returns, contains DatSize equivalent to the numeric value or symbol contained
        /// in input, if the conversion succeeded, or zero if the conversion failed. The conversion fails if the input parameter is a null
        /// reference (Nothing in Visual Basic), is not a number in a valid format, or represents a number less than MinValue or greater than MaxValue. This
        /// parameter is passed uninitialized.</param>
        /// <returns>
        /// True if the parse succeeded, false otherwise.
        /// </returns>
        /// <remarks>
        ///     <para>
        /// The string can take on the format of:
        /// <list type="bullet">
        ///             <item>128 MB</item>
        ///             <item>40.00 KB</item>
        ///             <item>78 gigabytes</item>
        ///             <item>1 terabyte</item>
        ///         </list>
        ///     </para>
        ///     <para>
        /// Lowercase representations will be accepted such as a b, kb, mb, etc but they will not be treated as "bits" they
        /// will be treated as "bytes". Their acceptance is simply to keep the function case insensitive.
        /// </para>
        /// </remarks>
        public static bool TryParse( string input, out DataSize dataSize )
        {
            return TryParse( input, false, out dataSize );
        }

        /// <summary>
        /// Converts the String representation of a number to its DataSize equivalent. A return value indicates whether the conversion succeeded or failed.
        /// </summary>
        /// <param name="input">A String object containing a number to convert.</param>
        /// <param name="requireUnit">True if a data size unit is required for the parse to succeed, false otherwise.</param>
        /// <param name="dataSize">When this method returns, contains DatSize equivalent to the numeric value or symbol contained
        /// in input, if the conversion succeeded, or zero if the conversion failed. The conversion fails if the input parameter is a null
        /// reference (Nothing in Visual Basic), is not a number in a valid format, or represents a number less than MinValue or greater than MaxValue. This
        /// parameter is passed uninitialized.</param>
        /// <returns>
        /// True if the parse succeeded, false otherwise.
        /// </returns>
        /// <remarks>
        ///     <para>
        /// The string can take on the format of:
        /// <list type="bullet">
        ///             <item>128 MB</item>
        ///             <item>40.00 KB</item>
        ///             <item>78 gigabytes</item>
        ///             <item>1 terabyte</item>
        ///         </list>
        ///     </para>
        ///     <para>
        /// Lowercase representations will be accepted such as a b, kb, mb, etc but they will not be treated as "bits" they
        /// will be treated as "bytes". Their acceptance is simply to keep the function case insensitive.
        /// </para>
        /// </remarks>
        [SuppressMessage( "Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "The switch statement is throwing it off." )]
        public static bool TryParse( string input, bool requireUnit, out DataSize dataSize )
        {

            dataSize = default( DataSize );

            // Make sure we have a string
            if ( String.IsNullOrEmpty( input ) )
                return false;

            // Trim both ends of input
            input = input.Trim( );

            string numberPart = null;
            string unitPart = null;

            // Find the position of the space, if any
            int spacePos = input.IndexOf( ' ' );
            if ( spacePos > -1 ) {
                // A space exists in the string
                // First segment is the number such as 3.04
                // Second segment is assumed to be the unit such as KB
                numberPart = input.Substring( 0, spacePos ).Trim( );
                unitPart = input.Substring( spacePos + 1 ).Trim( );
            }   // if
            else {
                if ( requireUnit ) {
                    return false;
                }   // if
                else {
                    numberPart = input;
                    unitPart = "B";
                }   // else
            }   // else

            // Parse the number into a decimal. Allow separators and such
            // If the parse fails, an exception will be thrown which will
            // propagate out to the caller.
            decimal number = 0m;
            if ( Decimal.TryParse( numberPart, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowThousands, null, out number ) ) {

                #region B.A.S.S. - Big Ass Switch Statement

                switch ( unitPart.ToUpperInvariant( ) ) {

                    case "B":
                    case "BYTE":
                    case "BYTES":
                    break;

                    case "K":
                    case "KB":
                    case "KBYTE":
                    case "KBYTES":
                    case "KILOBYTE":
                    case "KILOBYTES":
                    number *= KilobyteSize;
                    break;

                    case "M":
                    case "MB":
                    case "MEG":
                    case "MEGS":
                    case "MBYTE":
                    case "MBYTES":
                    case "MEGABYTE":
                    case "MEGABYTES":
                    number *= MegabyteSize;
                    break;

                    case "G":
                    case "GB":
                    case "GIG":
                    case "GIGS":
                    case "GBYTE":
                    case "GBYTES":
                    case "GIGABYTE":
                    case "GIGABYTES":
                    number *= GigabyteSize;
                    break;

                    case "T":
                    case "TB":
                    case "TBYTE":
                    case "TBYTES":
                    case "TERABYTE":
                    case "TERABYTES":
                    number *= TerabyteSize;
                    break;

                    default:
                    return false;

                }   // switch

                #endregion

                dataSize = new DataSize( (long)number );
                return true;

            }   // if

            return false;

        }

        /// <summary>
        /// Compares two DataSize structures and returns a value indicating whether one is less 
        /// than, equal to, or greater than the other.
        /// </summary>
        /// <param name="x">The first DataSize to compare.</param>
        /// <param name="y">The second DataSize to compare.</param>
        /// <returns>
        /// Less than zero, x is less than y. Zero, x equals y. Greater than zero, x is greater than y.
        /// </returns>
        [SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x" )]
        [SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y" )]
        public static int Compare( DataSize x, DataSize y )
        {
            return x.CompareTo( y );
        }

        /// <summary>
        /// Determines of two DataSize values are equal.
        /// </summary>
        /// <param name="x">The first DataSize to compare.</param>
        /// <param name="y">The second DataSize to compare.</param>
        /// <returns>True if the values are equal, false otherwise.</returns>
        [SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "x" )]
        [SuppressMessage( "Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "y" )]
        public static bool Equals( DataSize x, DataSize y )
        {
            return x.Equals( y );
        }

        #endregion

        #region Object Overrides

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">Another object to compare to.</param>
        /// <returns>
        /// true if obj and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        public override bool Equals( object obj )
        {

            if ( obj is DataSize )
                return Equals( (DataSize)obj );
            else
                return false;

        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override int GetHashCode( )
        {
            return bytes.GetHashCode( );
        }

        /// <summary>
        /// Returns a string representation of this DataSize using the default format.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> representing this data size.
        /// </returns>
        public override string ToString( )
        {
            return ToString( "A", CultureInfo.CurrentCulture );
        }

        /// <summary>
        /// Returns a string representation of this DataSize using the default format.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <returns>
        /// A <see cref="T:System.String"></see> representing this data size.
        /// </returns>
        /// <remarks>
        /// <para>
        /// The format specifier takes the form of: A99*
        /// </para>
        /// <para>
        /// Where A is any of the following characters:
        /// <list type="table">
        /// <listheader><term>Format Character</term></listheader>
        /// <item><term>A</term><description>Automatic. The unit of measurement will be the largest unit that is greater than or equal to 1.</description></item>
        /// <item><term>B</term><description>Bytes (B). No decimal digits will ever be displayed.</description></item>
        /// <item><term>K</term><description>Kilobytes (KB)</description></item>
        /// <item><term>M</term><description>Megabytes (MB)</description></item>
        /// <item><term>G</term><description>Gigabytes (GB)</description></item>
        /// <item><term>T</term><description>Terabytes (TB)</description></item>
        /// </list>
        /// </para>
        /// <para>
        /// The 99 represent a number from 0-99 that indicates the number of decimal places that will be
        /// included in the string. If bytes are specified as the unit of measurement, no decimal places will
        /// ever be used and this part of the format string will be ignored. If the precision is missing, up
        /// to two decimal places will be used.
        /// </para>
        /// <para>
        /// The asterisk (*) is an optional indicator that supresses the suffix. For example, if the value 39 KB
        /// is formatted as "K2*" then the output string would be "39.00" instead of "39.00 KB" because of the
        /// asterisk.
        /// </para>
        /// </remarks>
        public string ToString( string format )
        {
            return ToString( format, CultureInfo.CurrentCulture );
        }

        #endregion

        #region Operator Overloads

        /// <summary>
        /// Equality operator.
        /// </summary>
        /// <param name="left">The first DataSize to compare.</param>
        /// <param name="right">The second DataSize to compare.</param>
        /// <returns>True if <paramref name="left"/> and <paramref name="right"/> are equal, false otherwise.</returns>
        public static bool operator ==( DataSize left, DataSize right )
        {
            return left.bytes == right.bytes;
        }

        /// <summary>
        /// Inequality operator.
        /// </summary>
        /// <param name="left">The first DataSize to compare.</param>
        /// <param name="right">The second DataSize to compare.</param>
        /// <returns>False if <paramref name="left"/> and <paramref name="right"/> are equal, true otherwise.</returns>
        public static bool operator !=( DataSize left, DataSize right )
        {
            return left.bytes != right.bytes;
        }

        /// <summary>
        /// Greater than operator.
        /// </summary>
        /// <param name="left">The first DataSize to compare.</param>
        /// <param name="right">The second DataSize to compare.</param>
        /// <returns>True if <paramref name="left"/> is greater than <paramref name="right"/>, false otherwise.</returns>
        public static bool operator >( DataSize left, DataSize right )
        {
            return left.bytes > right.bytes;
        }

        /// <summary>
        /// Less than operator.
        /// </summary>
        /// <param name="left">The first DataSize to compare.</param>
        /// <param name="right">The second DataSize to compare.</param>
        /// <returns>True if <paramref name="left"/> is less than <paramref name="right"/>, false otherwise.</returns>
        public static bool operator <( DataSize left, DataSize right )
        {
            return left.bytes < right.bytes;
        }

        /// <summary>
        /// Greater than or equals operator.
        /// </summary>
        /// <param name="left">The first DataSize to compare.</param>
        /// <param name="right">The second DataSize to compare.</param>
        /// <returns>True if <paramref name="left"/> is greater than or equals <paramref name="right"/>, false otherwise.</returns>
        public static bool operator >=( DataSize left, DataSize right )
        {
            return left.bytes >= right.bytes;
        }

        /// <summary>
        /// Less than or equals operator.
        /// </summary>
        /// <param name="left">The first DataSize to compare.</param>
        /// <param name="right">The second DataSize to compare.</param>
        /// <returns>True if <paramref name="left"/> is less than or equals <paramref name="right"/>, false otherwise.</returns>
        public static bool operator <=( DataSize left, DataSize right )
        {
            return left.bytes <= right.bytes;
        }

        /// <summary>
        /// Unary plus operator.
        /// </summary>
        /// <param name="operand">The operand on which the operator operates.</param>
        /// <returns>The <paramref name="operand"/>.</returns>
        public static DataSize operator +( DataSize operand )
        {
            return operand;
        }

        /// <summary>
        /// Unary negation operator.
        /// </summary>
        /// <param name="operand">The operand on which the operator operates.</param>
        /// <returns>The <paramref name="operand"/>, negated.</returns>
        public static DataSize operator -( DataSize operand )
        {
            return new DataSize( -( operand.bytes ) );
        }

        /// <summary>
        /// Unary increment operator.
        /// </summary>
        /// <param name="operand">The operand on which the operator operates.</param>
        /// <returns>The <paramref name="operand"/> plus one.</returns>
        public static DataSize operator ++( DataSize operand )
        {
            return new DataSize( operand.bytes + 1 );
        }

        /// <summary>
        /// Unary decrement operator.
        /// </summary>
        /// <param name="operand">The operand on which the operator operates.</param>
        /// <returns>The <paramref name="operand"/>, minus one.</returns>
        public static DataSize operator --( DataSize operand )
        {
            return new DataSize( operand.bytes - 1 );
        }

        /// <summary>
        /// Addition operator.
        /// </summary>
        /// <param name="left">A DataSize to which <paramref name="right"/> will be added.</param>
        /// <param name="right">A second DataSize to add to <paramref name="left"/>.</param>
        /// <returns>A datasize that is the sum of <paramref name="left"/> and <paramref name="right"/>.</returns>
        public static DataSize operator +( DataSize left, DataSize right )
        {
            return new DataSize( left.bytes + right.bytes );
        }

        /// <summary>
        /// Subtraction operator.
        /// </summary>
        /// <param name="left">A DataSize to which <paramref name="right"/> will be subtracted.</param>
        /// <param name="right">A second DataSize to subtract from <paramref name="left"/>.</param>
        /// <returns>A datasize that is the difference of <paramref name="left"/> and <paramref name="right"/>.</returns>
        public static DataSize operator -( DataSize left, DataSize right )
        {
            return new DataSize( left.bytes - right.bytes );
        }

        /// <summary>
        /// Multiplication operator.
        /// </summary>
        /// <param name="left">A DataSize which will be multiplied by <paramref name="right"/>.</param>
        /// <param name="right">A DataSize by which <paramref name="left"/> will be multiplied.</param>
        /// <returns>A datasize that is the result of <paramref name="left"/> multiplied by <paramref name="right"/>.</returns>
        public static DataSize operator *( DataSize left, DataSize right )
        {
            return new DataSize( left.bytes * right.bytes );
        }

        /// <summary>
        /// Division operator.
        /// </summary>
        /// <param name="left">A DataSize which will be divided by <paramref name="right"/>.</param>
        /// <param name="right">A DataSize by which <paramref name="left"/> will be divided.</param>
        /// <returns>A datasize that is the result of <paramref name="left"/> divided by <paramref name="right"/>.</returns>
        public static DataSize operator /( DataSize left, DataSize right )
        {
            return new DataSize( left.bytes / right.bytes );
        }

        #endregion

        #region Conversion Operators

        /// <summary>
        /// Implicitly converts the specified <paramref name="operand"/> to <see cref="Int64"/>.
        /// </summary>
        /// <param name="operand">The DataSize to convert.</param>
        /// <returns>An <see cref="Int64"/> value that is the number of bytes represented by
        /// <paramref name="operand"/>.</returns>
        public static implicit operator long( DataSize operand )
        {
            return operand.bytes;
        }

        /// <summary>
        /// Implicitly converts the specified <paramref name="operand"/> to <see cref="Decimal"/>.
        /// </summary>
        /// <param name="operand">The DataSize to convert.</param>
        /// <returns>A <see cref="Decimal"/> value that is the number of bytes represented by
        /// <paramref name="operand"/>.</returns>
        public static implicit operator decimal( DataSize operand )
        {
            return operand.TotalBytes;
        }

        /// <summary>
        /// Explicitly converts the specified <paramref name="operand"/> to <see cref="DataSize"/>.
        /// </summary>
        /// <param name="operand">The <see cref="Int64"/> to convert.</param>
        /// <returns>A <see cref="DataSize"/> value that represents <paramref name="operand"/>
        /// number of bytes.</returns>
        [SuppressMessage( "Microsoft.Interoperability", "CA1406:AvoidInt64ArgumentsForVB6Clients", Justification = "VB 6 can't use overloaded operators." )]
        public static explicit operator DataSize( long operand )
        {
            return new DataSize( operand );
        }

        /// <summary>
        /// Explicitly converts the specified <paramref name="operand"/> to a string.
        /// </summary>
        /// <param name="operand">The <see cref="DataSize"/> to convert.</param>
        /// <returns>
        /// A string value that is the same as <see cref="M:DataSize.ToString"/>.
        /// </returns>
        public static explicit operator string( DataSize operand )
        {
            return operand.ToString( );
        }

        #endregion

        #region IFormattable Members

        /// <summary>
        /// Formats the value of the current instance using the specified format.
        /// </summary>
        /// <param name="format">The <see cref="T:System.String"></see> specifying the format to use.-or- null to use the default format defined for the type of the <see cref="T:System.IFormattable"></see> implementation.</param>
        /// <param name="formatProvider">The <see cref="T:System.IFormatProvider"></see> to use to format the value.-or- null to obtain the numeric format information from the current locale setting of the operating system.</param>
        /// <returns>
        /// A <see cref="T:System.String"></see> containing the value of the current instance in the specified format.
        /// </returns>
        public string ToString( string format, IFormatProvider formatProvider )
        {

            if ( String.IsNullOrEmpty( format ) )
                format = "A";

            Match formatMatch = formatRegex.Match( format );
            if ( formatMatch.Success ) {

                decimal value;
                int precision;
                string suffix;

                // Parse the precision specifier which determines how many decimal digits
                // will be displayed in the number portion of the return value.
                if ( formatMatch.Groups["precision"].Success ) {
                    // Try to parse the precision specifier and if we can't, default to zero
                    if ( Int32.TryParse( formatMatch.Groups["precision"].Value, out precision ) ) {
                        if ( precision > 99 || precision < 0 )
                            throw new FormatException( "Invalid format specifier." );
                    }   // if
                    else {
                        throw new FormatException( "Invalid format specifier." );
                    }   // else
                }   // if
                else {
                    precision = 2;
                }   // else

                switch ( formatMatch.Groups["unit"].Value.ToUpperInvariant( ) ) {

                    case "A":
                    // Automatic based on the size of the value
                    if ( Math.Truncate( Math.Abs( TotalTerabytes ) ) >= 1 )
                        goto case "T";
                    else if ( Math.Truncate( Math.Abs( TotalGigabytes ) ) >= 1 )
                        goto case "G";
                    else if ( Math.Truncate( Math.Abs( TotalMegabytes ) ) >= 1 )
                        goto case "M";
                    else if ( Math.Truncate( Math.Abs( TotalKilobytes ) ) >= 1 )
                        goto case "K";
                    else
                        goto case "B";

                    case "B":
                    value = TotalBytes;
                    suffix = ByteSuffix;
                    precision = 0;  // bytes cannot be fractional
                    break;

                    case "K":
                    value = TotalKilobytes;
                    suffix = KilobyteSuffix;
                    break;

                    case "M":
                    value = TotalMegabytes;
                    suffix = MegabyteSuffix;
                    break;

                    case "G":
                    value = TotalGigabytes;
                    suffix = GigabyteSuffix;
                    break;

                    case "T":
                    value = TotalTerabytes;
                    suffix = TerabyteSuffix;
                    break;

                    default:
                    throw new FormatException( "Invalid format specifier." );

                }   // switch

                if ( formatMatch.Groups["nosuffix"].Success ) {
                    // They want the value without the suffix
                    return String.Format( formatProvider, "{0:N" + precision + "}", value );
                }   // if
                else {
                    // They want the value with the sufffix
                    return String.Format( formatProvider, "{0:N" + precision + "} {1}", value, suffix );
                }   // else

            }   // if
            else {
                throw new FormatException( "Invalid format specifier." );
            }   // else

        }

        #endregion

        #region IComparable<DataSize> Members

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the other parameter.Zero This object is equal to other. Greater than zero This object is greater than other.
        /// </returns>
        public int CompareTo( DataSize other )
        {
            return bytes.CompareTo( other.bytes );
        }

        #endregion

        #region IComparable Members

        /// <summary>
        /// Compares the current instance with another object of the same type.
        /// </summary>
        /// <param name="obj">An object to compare with this instance.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance is less than obj. Zero This instance is equal to obj. Greater than zero This instance is greater than obj.
        /// </returns>
        /// <exception cref="T:System.ArgumentException">obj is not the same type as this instance. </exception>
        public int CompareTo( object obj )
        {

            if ( obj is DataSize )
                return CompareTo( (DataSize)obj );
            else
                throw new ArgumentException( "Compared value is not a DataSize.", "obj" );

        }

        #endregion

        #region IConvertible Members

        /// <summary>
        /// Returns the <see cref="T:System.TypeCode"></see> for this instance.
        /// </summary>
        /// <returns>
        /// The enumerated constant that is the <see cref="T:System.TypeCode"></see> of the class or value type that implements this interface.
        /// </returns>
        TypeCode IConvertible.GetTypeCode( )
        {
            return TypeCode.Int64;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent Boolean value using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A Boolean value equivalent to the value of this instance.
        /// </returns>
        bool IConvertible.ToBoolean( IFormatProvider provider )
        {
            return ( bytes != 0 );
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 8-bit unsigned integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 8-bit unsigned integer equivalent to the value of this instance.
        /// </returns>
        byte IConvertible.ToByte( IFormatProvider provider )
        {
            return (byte)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent Unicode character using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A Unicode character equivalent to the value of this instance.
        /// </returns>
        char IConvertible.ToChar( IFormatProvider provider )
        {
            throw new InvalidCastException( "Cannot convert from Einstein.DataSize to System.Char." );
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent <see cref="T:System.DateTime"></see> using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A <see cref="T:System.DateTime"></see> instance equivalent to the value of this instance.
        /// </returns>
        DateTime IConvertible.ToDateTime( IFormatProvider provider )
        {
            throw new InvalidCastException( "Cannot convert from Einstein.DataSize to System.DateTime." );
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent <see cref="T:System.Decimal"></see> number using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A <see cref="T:System.Decimal"></see> number equivalent to the value of this instance.
        /// </returns>
        decimal IConvertible.ToDecimal( IFormatProvider provider )
        {
            return (decimal)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent double-precision floating-point number using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A double-precision floating-point number equivalent to the value of this instance.
        /// </returns>
        double IConvertible.ToDouble( IFormatProvider provider )
        {
            return (double)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 16-bit signed integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 16-bit signed integer equivalent to the value of this instance.
        /// </returns>
        short IConvertible.ToInt16( IFormatProvider provider )
        {
            return (short)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 32-bit signed integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 32-bit signed integer equivalent to the value of this instance.
        /// </returns>
        int IConvertible.ToInt32( IFormatProvider provider )
        {
            return (int)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 64-bit signed integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 64-bit signed integer equivalent to the value of this instance.
        /// </returns>
        long IConvertible.ToInt64( IFormatProvider provider )
        {
            return bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 8-bit signed integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 8-bit signed integer equivalent to the value of this instance.
        /// </returns>
        sbyte IConvertible.ToSByte( IFormatProvider provider )
        {
            return (sbyte)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent single-precision floating-point number using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A single-precision floating-point number equivalent to the value of this instance.
        /// </returns>
        float IConvertible.ToSingle( IFormatProvider provider )
        {
            return (float)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent <see cref="T:System.String"></see> using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// A <see cref="T:System.String"></see> instance equivalent to the value of this instance.
        /// </returns>
        string IConvertible.ToString( IFormatProvider provider )
        {
            return ToString( "A", provider );
        }

        /// <summary>
        /// Converts the value of this instance to an <see cref="T:System.Object"></see> of the specified <see cref="T:System.Type"></see> that has an equivalent value, using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="conversionType">The <see cref="T:System.Type"></see> to which the value of this instance is converted.</param>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An <see cref="T:System.Object"></see> instance of type conversionType whose value is equivalent to the value of this instance.
        /// </returns>
        object IConvertible.ToType( Type conversionType, IFormatProvider provider )
        {

            IConvertible convertible = this;

            if ( conversionType == typeof( string ) )
                return convertible.ToString( provider );
            else if ( conversionType == typeof( DataSize ) )
                return this;
            else if ( conversionType == typeof( uint ) )
                return convertible.ToUInt32( provider );
            else if ( conversionType == typeof( int ) )
                return convertible.ToInt32( provider );
            else if ( conversionType == typeof( ulong ) )
                return convertible.ToUInt64( provider );
            else if ( conversionType == typeof( long ) )
                return convertible.ToInt64( provider );
            else if ( conversionType == typeof( float ) )
                return convertible.ToSingle( provider );
            else if ( conversionType == typeof( double ) )
                return convertible.ToDouble( provider );
            else if ( conversionType == typeof( decimal ) )
                return convertible.ToDecimal( provider );
            else if ( conversionType == typeof( byte ) )
                return convertible.ToByte( provider );
            else if ( conversionType == typeof( sbyte ) )
                return convertible.ToSByte( provider );
            else if ( conversionType == typeof( ushort ) )
                return convertible.ToUInt16( provider );
            else if ( conversionType == typeof( short ) )
                return convertible.ToInt16( provider );
            else if ( conversionType == typeof( bool ) )
                return convertible.ToBoolean( provider );
            else if ( conversionType == typeof( DateTime ) )
                return convertible.ToDateTime( provider );
            else if ( conversionType == typeof( char ) )
                return convertible.ToChar( provider );
            else
                throw new InvalidCastException( "Unable to convert DataSize to the requested type." );

        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 16-bit unsigned integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 16-bit unsigned integer equivalent to the value of this instance.
        /// </returns>
        ushort IConvertible.ToUInt16( IFormatProvider provider )
        {
            return (ushort)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 32-bit unsigned integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 32-bit unsigned integer equivalent to the value of this instance.
        /// </returns>
        uint IConvertible.ToUInt32( IFormatProvider provider )
        {
            return (uint)bytes;
        }

        /// <summary>
        /// Converts the value of this instance to an equivalent 64-bit unsigned integer using the specified culture-specific formatting information.
        /// </summary>
        /// <param name="provider">An <see cref="T:System.IFormatProvider"></see> interface implementation that supplies culture-specific formatting information.</param>
        /// <returns>
        /// An 64-bit unsigned integer equivalent to the value of this instance.
        /// </returns>
        ulong IConvertible.ToUInt64( IFormatProvider provider )
        {
            return (ulong)bytes;
        }

        #endregion

        #region IEquatable<DataSize> Members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the other parameter; otherwise, false.
        /// </returns>
        public bool Equals( DataSize other )
        {
            return bytes == other.bytes;
        }

        #endregion

        #region IXmlSerializable Members

        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema( )
        {
            return null;
        }

        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized.</param>
        void IXmlSerializable.ReadXml( System.Xml.XmlReader reader )
        {
            this.bytes = reader.ReadElementContentAsLong( );
        }

        void IXmlSerializable.WriteXml( System.Xml.XmlWriter writer )
        {
            writer.WriteValue( this.bytes );
        }

        #endregion

    }   // class

}   // namespace

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.

WaterboardSharePointSeriously, every time I work on a SharePoint project, I hate the technology more and more. I still stand by my position that for 90% of most document collaboration and portal projects, it does the job superbly. However, that 10% that you need to customize is a really painful 10%. In many cases once you cross that line where custom development is needed, the curve swings sharply upward because of the sheer amount of XML hell and redundancy required just to get to “hello world”.

But I think the thing I hate the most about custom SharePoint development is the total lack of tools support. (Well either that or the hundreds of kilobytes of XML-encoded HTML embedded in schema definitions.) Sure, we have WSPBuilder and VseWss (which is even more fragile than SharePoint itself) but why is Microsoft lagging so far behind in this area? Microsoft has always had a great reputation for having a strong developer story behind every core product. But with SharePoint, sometimes I feel like modifying the source code would be easier than deploying minor customizations such as a field that uses a regular expression for validation.

Really, the gap between ASP.NET development experience and SharePoint experience should not be so extreme.

Apr 062009

I got one of those emails today that had more Fwd: headers than actual content, but I read it and I really liked the analogy so I decided to post it.

When I was teaching economics, I would simply draw a wagon on the chalk board with people riding in the wagon and people pulling the wagon and I would ask the students what happens when there is more riding than pulling?  The answer is obvious, the wagon stops.  It is the same with an economic system.  Someone has to pull the wagon.

And this one…

An economics professor at Texas Tech said he had never failed a single student before but had, once, failed an entire class. That class had insisted that socialism worked; and that no one would be poor and no one would be rich – a great equalizer. The professor then said ok, we will have an experiment in this class on socialism. All grades would be averaged and everyone would receive the same grade so no one would fail and no one would receive an A.

After the first test the grades were averaged and everyone got a B. The students who studied hard were upset and the students who studied little were happy.  But, as the second test rolled around, the students who studied little had studied even less and the ones who studied hard decided they wanted a free ride too; so they studied little. The second test average was a D!  No one was happy. When the 3rd test rolled around the average was an F.

The scores never increased as bickering, blame, name calling all resulted in hard feelings and no one would study for the benefit of anyone else.  All failed, to their great surprise, and the professor told them that socialism would also ultimately fail because when the reward is great, the effort to succeed is great; but when government takes all the reward away; no one will try or want to succeed.