PowerShell Team has announced FxCop rules for cmdlet authors.

List of rules

  • AcceptForceParameterWhenCallingShouldContinue
  • AllCmdletsShouldAcceptPipelineInput
  • CallShouldProcessOnlyIfDeclaringSupport
  • DefineCmdletInTheCorrectNamespace
  • DoNotAccessPipelineParametersOutsideProcessRecord
  • DoNotCallCertainHostMethods
  • DoNotUseConsoleApi
  • FollowCmdletClassNamingConvention
  • OverrideProcessRecordIfAcceptingPipelineInput
  • ParameterShouldHaveConsistentTypePerNoun
  • UseCredentialAttributeForPSCredentialParameter
  • UseOnlyApprovedCharactersInVerbsAndNouns
  • UseOnlyStandardVerbs
  • UsePascalCasingInVerbsAndNouns
  • UseRecommendedParameterTypes
  • UseSingularNouns
  • UseSingularParameterNames
  • UseSwitchParameterInsteadOfBoolean

I wanted to post this last night but I did not have an internet connection.

The best part about being a developer is that when software drives you nuts, many times you can do something about it that mere mortals cannot. That’s the case with TweetDeck. I love it but I am sick of having to move windows around to keep it in view. I have a wide screen so I wanted to dedicate a band of space to it, much like how the Vista sidebar worked. When I maximize other windows, they should not obscure the taskbar or TweetDeck.

The title of this post is a little misleading for two reasons…

  1. PowerShell really has nothing to do with it. It’s just the method I chose to kick off this little hack. The actual implementation is all Win32 API via C#.
  2. TweetDeck is just the application that happened to drive me to write this. You could just as easily change one line to dock Windows Live Messenger instead.

So the code is shown below. It’s nearly all in C# but PowerShell will dynamically compile it when you run the script. No exe’s needed. Just run the script and…

  • TweetDeck’s caption and window border will be removed
  • It will be docked against the right hand side of your primary screen (hard coded, sorry)
  • Maximized windows will adjust to the new workspace size

A couple caveats though.

  • Resize TweetDeck to the size you want it before doing this
  • Once docked, there’s currently no way to undock. So just close it from the taskbar.
  • TweetDeck is buggy in "single column view" as many UI elements are cut off. It’s best to turn off "use narrow columns" first.

Code is below. Enjoy.

$DockProcessName = 'tweetdeck'

Add-Type -Language CSharpVersion3 @"
using System;
using System.Runtime.InteropServices;

public static class WindowDockUtil
{

    #region Public Methods

    /// <summary>
    /// Docks the window to the right side of the screen.
    /// </summary>
    /// <param name="hWnd">The window handle.</param>
    public static void Dock( IntPtr hWnd )
    {

        if ( hWnd == IntPtr.Zero ) {
            return;
        }   // if

        ulong windowLong = GetWindowLong( hWnd, GWL_STYLE );
        windowLong &= ~WS_BORDER;
        windowLong &= ~WS_CAPTION;
        windowLong &= ~WS_THICKFRAME;

        SetWindowLong( hWnd, GWL_STYLE, windowLong );

        var abd = new APPBARDATA( );
        abd.cbSize = Marshal.SizeOf( abd );
        abd.hWnd = hWnd;
        abd.uCallbackMessage = RegisterWindowMessage( "AppBarMessage" );

        // Create AppBar
        SHAppBarMessage( ABMsg.ABM_NEW, ref abd );

        // Get the Window's size
        RECT wc = new RECT( );
        if ( !GetWindowRect( hWnd, ref wc ) ) {
            return;
        }   // if

        int screenWidth = GetSystemMetrics( SM_CXSCREEN );
        int screenHeight = GetSystemMetrics( SM_CYSCREEN );
        int borderWidth = GetSystemMetrics( SM_CXSIZEFRAME );

        // Set the app bar to dock to the right
        abd.uEdge = ABEdge.ABE_RIGHT;
        abd.rc.top = 0;
        abd.rc.bottom = screenHeight;
        abd.rc.right = screenWidth;
        abd.rc.left = abd.rc.right - ( wc.right - wc.left ) + ( borderWidth * 2 );

        // Set AppBar Size
        SHAppBarMessage( ABMsg.ABM_SETPOS, ref abd );

        // Move Window to New Position
        MoveWindow( abd.hWnd, abd.rc.left, abd.rc.top, abd.rc.right - abd.rc.left, abd.rc.bottom - abd.rc.top, true );

    }

    ///// <summary>
    ///// Removes the window from the side of the screen.
    ///// </summary>
    ///// <param name="hWnd">The handle.</param>
    //public static void UndockWindow( IntPtr hWnd )
    //{

    //    if ( hWnd == IntPtr.Zero ) {
    //        return;
    //    }   // if

    //    var abd = new APPBARDATA( );
    //    abd.cbSize = Marshal.SizeOf( abd );
    //    abd.hWnd = hWnd;

    //    // Remove AppBar
    //    SHAppBarMessage( ABMsg.ABM_REMOVE, ref abd );

    //    // How should I store state between Dock and Undock?
    //    ulong windowLong = GetWindowLong( hWnd, GWL_STYLE );
    //    windowLong |= WS_BORDER;
    //    windowLong |= WS_CAPTION;

    //    SetWindowLong( hWnd, GWL_STYLE, windowLong );

    //}

    #endregion

    #region Interop

    #region AppBar

    [StructLayout( LayoutKind.Sequential )]
    private struct APPBARDATA
    {
        public int cbSize;
        public IntPtr hWnd;
        public int uCallbackMessage;
        public ABEdge uEdge;
        public RECT rc;
        public IntPtr lParam;
    }

    private enum ABMsg : int
    {
        ABM_NEW = 0,
        ABM_REMOVE = 1,
        ABM_QUERYPOS = 2,
        ABM_SETPOS = 3,
        ABM_GETSTATE = 4,
        ABM_GETTASKBARPOS = 5,
        ABM_ACTIVATE = 6,
        ABM_GETAUTOHIDEBAR = 7,
        ABM_SETAUTOHIDEBAR = 8,
        ABM_WINDOWPOSCHANGED = 9,
        ABM_SETSTATE = 10
    }

    private enum ABNotify : int
    {
        ABN_STATECHANGE = 0,
        ABN_POSCHANGED = 1,
        ABN_FULLSCREENAPP = 2,
        ABN_WINDOWARRANGE = 3
    }

    private enum ABEdge : int
    {
        ABE_LEFT = 0,
        ABE_TOP = 1,
        ABE_RIGHT = 2,
        ABE_BOTTOM = 3
    }

    [DllImport( "Shell32", CallingConvention = CallingConvention.StdCall )]
    private static extern uint SHAppBarMessage( ABMsg dwMessage, ref APPBARDATA pData );

    #endregion

    #region Window Management

    [DllImport( "User32" )]
    private static extern int RegisterWindowMessage( string msg );

    [DllImport( "User32" )]
    private static extern IntPtr FindWindow( string lpClassName, string lpWindowName );

    [DllImport( "User32" )]
    private static extern bool MoveWindow( IntPtr hWnd, int x, int y, int cx, int cy, bool repaint );

    [DllImport( "User32" )]
    private static extern bool GetWindowRect( IntPtr hWnd, ref RECT rect );

    [StructLayout( LayoutKind.Sequential )]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [DllImport( "User32" )]
    private static extern int GetSystemMetrics( int Index );

    private const int SM_CXSCREEN = 0;
    private const int SM_CYSCREEN = 1;
    private const int SM_CXSIZEFRAME = 32;

    #endregion

    #region GetWindowLong/SetWindowLong

    [DllImport( "User32", EntryPoint = "SetWindowLongPtr" )]
    private static extern ulong SetWindowLong64( IntPtr hWnd, int nIndex, ulong dwNewLong );

    [DllImport( "User32", EntryPoint = "GetWindowLongPtr" )]
    private static extern ulong GetWindowLong64( IntPtr hWnd, int nIndex );

    [DllImport( "User32", EntryPoint = "SetWindowLong" )]
    private static extern uint SetWindowLong32( IntPtr hWnd, int nIndex, uint dwNewLong );

    [DllImport( "User32", EntryPoint = "SetWindowLong" )]
    private static extern uint GetWindowLong32( IntPtr hWnd, int nIndex );

    private const int GWL_WNDPROC =    ( -4 );
    private const int GWL_HINSTANCE =  ( -6 );
    private const int GWL_HWNDPARENT = ( -8 );
    private const int GWL_STYLE =      ( -16 );
    private const int GWL_EXSTYLE =    ( -20 );
    private const int GWL_USERDATA =   ( -21 );
    private const int GWL_ID =         ( -12 );

    private static ulong GetWindowLong( IntPtr hWnd, int nIndex )
    {
        if ( IntPtr.Size == 4 ) {
            return GetWindowLong32( hWnd, nIndex );
        }   // if
        else if ( IntPtr.Size == 8 ) {
            return GetWindowLong64( hWnd, nIndex );
        }   // else if
        else {
            throw new NotSupportedException( "Unsupported platform." );
        }   // else
    }

    private static ulong SetWindowLong( IntPtr hWnd, int nIndex, ulong dwNewLong )
    {
        if ( IntPtr.Size == 4 ) {
            return SetWindowLong32( hWnd, nIndex, (uint)dwNewLong );
        }   // if
        else if ( IntPtr.Size == 8 ) {
            return SetWindowLong64( hWnd, nIndex, dwNewLong );
        }   // else if
        else {
            throw new NotSupportedException( "Unsupported platform." );
        }   // else
    }

    #endregion

    #region Window Styles

    private const ulong WS_OVERLAPPED = 0x0000;
    private const ulong WS_POPUP = 0x80000000;
    private const ulong WS_CHILD = 0x40000000;
    private const ulong WS_MINIMIZE = 0x20000000;
    private const ulong WS_VISIBLE = 0x10000000;
    private const ulong WS_DISABLED = 0x8000000;
    private const ulong WS_CLIPSIBLINGS = 0x4000000;
    private const ulong WS_CLIPCHILDREN = 0x2000000;
    private const ulong WS_MAXIMIZE = 0x1000000;
    private const ulong WS_BORDER = 0x800000;
    private const ulong WS_DLGFRAME = 0x400000;
    private const ulong WS_VSCROLL = 0x200000;
    private const ulong WS_HSCROLL = 0x100000;
    private const ulong WS_SYSMENU = 0x80000;
    private const ulong WS_THICKFRAME = 0x40000;
    private const ulong WS_GROUP = 0x20000;
    private const ulong WS_TABSTOP = 0x10000;
    private const ulong WS_MINIMIZEBOX = 0x20000;
    private const ulong WS_MAXIMIZEBOX = 0x10000;
    private const ulong WS_CAPTION = WS_BORDER | WS_DLGFRAME;
    private const ulong WS_TILED = WS_OVERLAPPED;
    private const ulong WS_ICONIC = WS_MINIMIZE;
    private const ulong WS_SIZEBOX = WS_THICKFRAME;
    private const ulong WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW;
    private const ulong WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
    private const ulong WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU;
    private const ulong WS_CHILDWINDOW = WS_CHILD;

    #endregion

    #endregion

}   // class
"@

if ($DockProcess = Get-Process $DockProcessName -ea 0) {
   [WindowDockUtil]::Dock($DockProcess.MainWindowHandle)
}
else {
    Write-Warning "Cannot find the $DockProcessName process."
}

image

It’s way too hard to get an image next to the text content of a Button, TextBlock, ListBoxItem, TreeViewItem, etc. Coming from a Windows Forms background where many controls had an Image property for displaying a small glyph next to text content, I am constantly frustrated by the verbose XAML required to achieve the same in WPF and Silverlight.

<StackPanel>
    <Button>
        <DockPanel>
            <Image Source="Images/Alert.png" />
            <TextBlock Text="Alerts" />
        </DockPanel>
    </Button>
    <Button>
        <DockPanel>
            <Image Source="Images/Color-Blue.png" />
            <TextBlock Text="Blue Category" />
        </DockPanel>
    </Button>
    <Button>
        <DockPanel>
            <Image Source="Images/Color-Green.png" />
            <TextBlock Text="Green Category" />
        </DockPanel>
    </Button>
    <Button>
        <DockPanel>
            <Image Source="Images/Color-Red.png" />
            <TextBlock Text="Red Category" />
        </DockPanel>
    </Button>
</StackPanel>

Well using a WPF markup extension in XAML we can make the code look a little less clunky. Markup extensions (which derive from System.Windows.MarkupExtension) are instantiated similarly to the way attributes are declared in C#. That is to say, there can be positional and/or named parameters. By overriding the ProvideValue you can use those parameters to construct whatever object structure you want and return it. The XAML parser will use this in place of your markup extension when it finds it.

<StackPanel>
    <Button Content="{e:Content Alerts, Image=Images/Alert.png}" />
    <Button Content="{e:Content Blue Category, Image=Images/Color-Blue.png}" />
    <Button Content="{e:Content Green Category, Image=Images/Color-Green.png}" />
    <Button Content="{e:Content Red Category, Image=Images/Color-Red.png}" />
</StackPanel>

Unfortunately Silverlight does not allow you to create custom markup extensions yet so this same technique cannot be applied to Silverlight where the situation is arguably worse with all the toolkit namespaces and such.

The code for the ContentExtension is very straightforward so I’m just going to add it to the end of the post instead of going through the hassle of uploading a project. Enjoy.

/// <summary>
/// A XAML Markup Extension that allows you to combine simple text content with an image
/// alongside instead of having to manually nest the image and text in a panel.
/// </summary>
public class ContentExtension : MarkupExtension
{

    // constructor with positional parameter
    public ContentExtension( string text )
    {
        Text = text;
    }

    // image must be specified as a named parameter
    public ImageSource Image
    {
        get;
        set;
    }

    public string Text
    {
        get;
        set;
    }

    public override object ProvideValue( IServiceProvider serviceProvider )
    {
        return new DockPanel {
            Children = {
                new Image {
                    Source = Image,
                    Stretch = Stretch.None,
                    VerticalAlignment = VerticalAlignment.Center,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    Margin = new Thickness( 5 )
                },
                new TextBlock {
                    VerticalAlignment = VerticalAlignment.Center,
                    Text = Text
                }
            }
        };
    }

}   // class

Content Markup Extension

If you’re a system administrator (or like many developers), chances are you use PowerShell a lot and have the PowerShell console or PowerShell ISE on your Windows 7 taskbar. On Windows Vista and on Windows Server 2008 prior to R2 I was annoyed by having both ISE and console on the quick launch bar or pinned to the start menu.

In Windows 7 here’s a neat tip. Pin the PowerShell console to the taskbar and not the PowerShell ISE. When you right click on the icon, you get a handy jump list which includes not only "Run As Administrator" but also "Open PowerShell ISE". All in the space of a single tile.

Windows PowerShell Jump List

Download MathEvalConverter.zip

Background

With the name Einstein, people typically assume I’m good at math. I have the utmost respect for the physicists and mathemeticians of our time, prior, and beyond. But to be honest, math is not my strong suit. In fact I have great difficulty simply adding or subtracting numbers without the use of a calculator. This deficiency actually worked to my advantage last month when I was observing the users of an application I had recently dogfed.

The application had several numeric input fields that took in costs, prices, commissions, etc. Like any run of the mill business application, these inputs affected various calculations that updated the UI accordingly. But while observing the users I kept noticing a very peculiar behavior. When they would type into a box for Cost, Price, etc. they would whip out a small desk calculator and add up some numbers before typing them into the box.

As it turns out, the values I took for granted as a pre-calculated input often require various "in-your-head" steps before arriving at what I assumed was a known "input".

"The cost of the product is normally $15 but I knocked it down by $3."

Given my mathematical challenges, I was very sympathetic to this situation. I don’t own a desk calculator but I am very familiar with the Start -> Run -> calc.exe ceremony. If I happen to have a PowerShell window open I’ll use that instead.

Did I miss a key requirement? Should there have been more input fields? Not really because the calculation steps are not necessarily formal aspects of the system. They’re mostly things people might normally do in their head had they not happen to have a calculator in front of them. And the type of calculations they perform vary from case to case.

Then I remembered… I encounter this same situation all the time when working in Excel. But Excel’s primary feature is the fact that you can type a "formula" or expression anywhere you’d put a constant value. Wouldn’t it be great if the text boxes in my application offered the same functionality? Well by the end of this post, they will!

The Plan

The idea is that my input boxes would allow the user to enter a number as usual, but if they entered an expression such as "2+2", committing the value would enter "4" into the field. The expression doesn’t need to be preserved, so the value of the field can still be backed by a simple number. Also, I only need to implement very basic expressions. I’ll stick to addition, subtraction, multiplication, division, grouping, and any combination of the above.

I thought about where I might add this functionality. I could subclass TextBox, but then the cells in a DataGrid would need to be addressed separately. I could probably have done it with a behavior but that didn’t seem appropriate either. In the end I decided to implement an IValueConverter. I was already using one to provide currency formatting in the control.

The end result would look like the working example shown below.

The Math Parser

Because this application was an in-browser Silverlight application, I could avoid having to write my own math parser by taking advantage of the HTML bridge and the Eval() method. In short, I would pass the expression to the JavaScript engine to evaluate as if it were a line of code. Of course, I would need to validate the input first to ensure a malicious user could not take advantage of this fact.

To isolate this shortcut into replacable component, I extracted an IMathEvaluator interface that would be defined as follows.

public interface IMathEvaluator {
    bool TryEvaluate(string expression, out decimal value);
}

This allows me to include my JavaScript hack for the sake of this post while admitting that a less lazy developer could substitute a better implementation. One nice thing about this implementation is that I don’t need to determine if the user entered a number or expression. Even if they entered a simple number like 1, I still treat it as an expression.

The implementation is pretty simple too. It simply checks the input against a Regex to ensure it’s not going to pass some malicious payload to the JavaScript engine then calls Eval() via the Silverlight HTML bridge.

public sealed class JScriptMathEvaluator : IMathEvaluator
{
    public bool TryEvaluate(string expression, out decimal value ) {

        // When there's no text, just return zero
        if ( expression == null || expression.Trim().Length == 0 ) {
            value = 0;
            return true;
        }
        
        // remove currency symbols and commas
        // these are added when we format the number but we want to remove them before
        // parsing the text value because it would invalidate the JavaScript syntax
        var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
        expression = expression.Replace( numberFormat.CurrencySymbol, "" );
        expression = expression.Replace( numberFormat.CurrencyGroupSeparator, "" );
        expression = expression.Replace( numberFormat.NumberGroupSeparator, "" );
        expression = expression.Replace( numberFormat.PercentGroupSeparator, "" );

        // Ensure that a simple expression consisting of only digits,
        // parenthases, and four operators (+, -, *, /) are entered.
        // Never pass non-validated input to JavaScript!
        if (Regex.IsMatch(expression, @"^[0-9\.\+\-\*\/\(\)\s]+$" ) ) {

            try {
                object eval = System.Windows.Browser.HtmlPage.Window.Eval( expression );
                value = Convert.ToDecimal(eval);
                return true;
            }   // try
            catch ( Exception ex ) {
                // trace exception or whatever
                // but let the function return false
            }   // catch

        }   // if

        value = 0;
        return false;

    }
}

The Value Converter

In order to convert the user’s input into a number I’ll need to invoke a conversion process when the value of the TextBox changes. I was already doing this with a FormattingConverter that would apply currency formatting to the number and then parse the text value with NumberStyles.

So let’s just derive a class from FormattingConverter with support for the ConvertBack method. We’ll call it MathEvalConverter.

public sealed class MathEvalConverter : FormattingConverter
{

    public MathEvalConverter( )
    {
        MathEvaluator = new JScriptMathEvaluator( );
    }

    public IMathEvaluator MathEvaluator
    {
        get;
        set;
    }

    protected override object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
    {

        // View -> ViewModel

        string expression = System.Convert.ToString( value );

        decimal numberValue;
        if ( MathEvaluator != null && MathEvaluator.TryEvaluate( expression, out numberValue ) ) {
            return numberValue;
        }   // if

        // We couldn't parse the expression.
        // Let silverlight reject it because:
        //   - if we throw an exception, it Silverlight bypasses validation events
        //   - if we return DependencyProperty.UnsetValue, the input will just disappear with no error!
        // By returning the unconverted value back to Silverlight, at least they'll see a conversion error.

        return base.ConvertBack( value, targetType, parameter, culture );

    }

}   // class

The converter doesn’t specifically use the JavaScript implementation. It goes through the IMathEvaluator interface which happens to be implemented by JScriptMathEvaluator.

MathEvalConverter In Action

We can apply the MathEvalConverter to any two-way binding between a numeric property on the binding source and a string property on a control. Since it derives from FormattingConverter, we can supply a format string to pretty format the numbers.

<UserControl>
    <FrameworkElement.Resources>
        <Local:MathEvalConverter x:Key="Eval" Format="C2" />
        <Local:FormattingConverter x:Key="Date" Format="d" />
    </FrameworkElement.Resources>
    <Form:DataForm Header="Expense Report">
        <StackPanel>

            <Form:DataField Label="Reported By">
                <TextBox Text="{Binding Name, Mode=TwoWay}" />
            </Form:DataField>

            <Form:DataField Label="Expense Date">
                <TextBox Text="{Binding Date, Converter={StaticResource Date}, Mode=TwoWay}" />
            </Form:DataField>

            <Form:DataField Label="Meals">
                <TextBox Text="{Binding Meals, Converter={StaticResource Eval}, Mode=TwoWay}" />
            </Form:DataField>

        </StackPanel>
    </Form:DataForm>
</UserControl>

Summary

Enough talk. Download the code, and let me know what you think. I have no idea if anyone finds these posts useful if you don’t post comments!

Download MathEvalConverter.zip

These aren’t your typical, obvious tips. Head over to the Visual Studio Blog to learn some WPF performance tips that the team picked up during the development of Visual Studio 2010. Some of the performance issues required changes to WPF that we all will benefit from in .NET 4. Others are clever ways to work around various idiosyncracies of WPF.

Take this for example.

Optimize for Remote Desktop scenarios : Use scrolling hint

Also on the topic of Remote Desktop, one area where we needed additional support from WPF was for scrolling in the text editor. As I mentioned above, when in a remote session, all WPF content is transmitted as a bitmap. When the text editor scrolls by a line, that means that the entire contents of the editor region needs to be retransmitted as a bitmap. This, of course can be expensive – the larger the area of the text view, the larger the bitmap and the slower it will be. Fortunately, WPF 4.0 now knows how to issue a “ScrollWindow” command to the remote desktop session which drastically reduces the amount of information transferred across the wire. Only the scroll operation itself (very short) and the newly-exposed line need to be transmitted. To take advantage of this new operation, you need to use the property VisualScrollableAreaClip. There are some restrictions on where this can be used, so read the documentation carefully.