Open Explorer Window Here

From time to time, while working within a PowerShell command window, I want to open Windows Explorer with the current directory.

In order to achieve this “reverse command prompt here” functionality you just need to use Invoke-Item as follows:

ii .

(Btw, in the days of the legacy command prompt, I used to type "START ." to do the same.)

How To Listen To The File System With PowerShell?

Currently I am building the prototype of a PowerShell-based automation solution. From the high level perspective it will work like a Print Spooler meaning that some kind of watchdog listens in the background to a particular directory for incoming files in order to process them or rather pass them to a processing engine.

With PowerShell 2.0 it is an easy task to leverage the .NET Framework’s FileSystemWatcher Class to establish such a file system watchdog:

PS C:\Scripts> $fsw = New-Object System.IO.FileSystemWatcher
PS C:\Scripts> $fsw

NotifyFilter          : FileName, DirectoryName, LastWrite
EnableRaisingEvents   : False
Filter                : *.*
IncludeSubdirectories : False
InternalBufferSize    : 8192
Path                  :
Site                  :
SynchronizingObject   :
Container             :

PS C:\Scripts>

Obviously, a freshly initialized instance of System.IO.FileSystemWatcher is very roughly prepared to watch for file system changes. Five properties are preset, namely NotifyFilter (specifies the changes to watch for in a file system object), EnableRaisingEvents (indicates whether events are raised or not – yes, we want!), Filter (specifies what files are monitored), IncludeSubdirectories (indicates whether subdirectories are monitored or not), and InternalBufferSize. (Please look in the MSDN for more info on the FileSystemWatcher class.)

So, how about a PowerShell function to initialize a .NET FileSystemWatcher object with proper values for Patch, Filter, NotifyFilter, IncludeSubdirectories, and EnableRaisingEvents? Here we go!

function New-FSWatcher
{
    <#
    .SYNOPSIS
        Initializes a .NET file system watcher

    .DESCRIPTION
        Initializes a .NET file system watcher, given the specified directory and optionally the type of files to monitor.

    .PARAMETER Path
        Specifies the path of an existing directory to watch.

    .PARAMETER Filter
        Determines what files are monitored in Path. (Defaults to *.*)

    .PARAMETER NotifyFilter
        Specifies the types of changes to watch for. Valid types are FileName (default), DirectoryName (default), Attributes, Size, LastWrite (default), LastAccess (default), CreationTime, and Security.

    .PARAMETER Recurse
        Indicates whether subdirectories within the Path should be monitored.

    .OUTPUTS
        Returns a .NET FileSystemWatcher object

    .EXAMPLE
        PS C:\> $fsw = New-FSWatcher -Path 'C:\Temp'

        This command initializes a file system watcher for *.* in C:\Temp.

    .EXAMPLE
        PS C:\> $fswxml = New-FSWatcher C:\Temp *.xml -Recurse

        This command initializes a file system watcher for *.xml in C:\Temp including its subdirectories.

    .LINK
        Start-FSWatcher
        Register-FSWatcherEventHandler
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path
        ,
        [Parameter(Mandatory=$false, Position=1)]
        [String]
        $Filter = '*.*'
        ,
        [Parameter(Mandatory=$false)]
        [System.IO.NotifyFilters]
        $NotifyFilter = ('FileName','LastWrite','LastAccess')
        ,
        [Switch]
        $Recurse
    )

    if ($PSCmdlet.ShouldProcess("$Path\$Filter", "Initialize FileSystemWatcher"))
    {
        $FileSystemWatcher = New-Object System.IO.FileSystemWatcher
        $FileSystemWatcher.Path = $Path
        $FileSystemWatcher.Filter = $Filter
        $FileSystemWatcher.NotifyFilter = $NotifyFilter
        if ($Recurse)
        {
            $FileSystemWatcher.IncludeSubdirectories = $true
        }
        $FileSystemWatcher.EnableRaisingEvents = $true
        $FileSystemWatcher
    }
}

That’s only half the battle. With New-FSWatcher you are able to initialize a FileSystemWatcher. But how to start monitoring?

If you look at the methods of System.IO.FileSystemWatcher you will sooner or later discover WaitForChanged(), "a synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor and the time (in milliseconds) to wait before timing out".

So, with WaitForChanged() it is perfectly possible to stop script processing for a given amount of maximum time in order to wait for a file. Apart from the fact that I have a watchdog in mind that should wait for file system events in the background, I will provide a function to leverage a synchronous (foreground) wait for files anyways:

function Start-FSWatcher
{
    <#
    .SYNOPSIS
        Listens the file system changes for a given amount of time.

    .DESCRIPTION
        Uses a previously configured FileSystemWatcher to listen for changes in a directory using a synchronous method that returns specific information on each change that occurred, given the type of change you want to monitor and the time to wait before timing out.

    .PARAMETER FileSystemWatcher
        Specifies a FileSystemWatcher object.

    .PARAMETER Type
        Specifies the type of change you want to monitor. Valid type are All (default), Changed, Created, Deleted, Disposed, Error, and Renamed.

    .PARAMETER TimeOut
        Specifies the timeout in ms. (Defaults to 10000)

    .PARAMETER Infinite
        Specifies to watch infinitely. (Not recommended. Consider registering events instead.)

    .OUTPUTS
        A structure containing specific information on each change that occurred.

    .NOTES
        Hidden files are NOT ignored.

    .EXAMPLE
        PS C:\> $fsw = New-FSWatcher -Path 'C:\Temp' -Filter 'jobfinished.txt'
        PS C:\> Start-FSWatcher -FSW $fsw

        These two commands initialize and use a FileSystemWather for C:\Temp. The watcher listens 10 seconds (default) for any changes in the directory.

    .EXAMPLE
        PS C:\> New-FSWatcher C:\Temp *.xml | Start-FSWatcher -Infinite

        This pipeline shows how to start an infinite FileSystemWatcher for any change on any XML file in C:\Temp.

    .LINK
        New-FSWatcher
        Register-FSWatcherEventHandler
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [Alias("FSW")]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileSystemWatcher]
        $FileSystemWatcher
        ,
        [Parameter(Mandatory=$false)]
        [ValidateSet('All','Changed', 'Created', 'Deleted', 'Disposed', 'Error', 'Renamed')]
        [String]
        $Type = 'All'
        ,
        [Parameter(Mandatory=$false)]
        [Int]
        $TimeOut = 10000
        ,
        [Switch]
        $Infinite
    )

    if ($PSCmdlet.ShouldProcess("$($FileSystemWatcher.Path)", "WaitForChanged($Type, $TimeOut)"))
    {
        do
        {
            $FileSystemChange = $FileSystemWatcher.WaitforChanged($Type, $TimeOut)
            if (!$FileSystemChange.TimedOut)
            {
                $FileSystemChange
            }
        }
        while ($Infinite)
    }
}

Please note that Start-FSWatcher will stop monitoring the file system as soon as the first change occurs that matches the FileSystemWatcher object’s configuration (unless the Infinite switch has been specified which causes the function to invoke the WaitForChanges() method again and again till the end of time). If you want to wait for a particular file you can define a FileSystemWatcher object and setup its Filter property to match the exact file name. Therefore, concerning a specific file name to be monitored, there’s no need to change the function.

To return to square one finally: how to define the file system watcher event handlers that will be fired on change, creation, deletion, or renaming of a file or directory? This is really straightforward. Basically you need to use the Register-ObjectEvent Cmdlet with a previously initialized FileSystemWatcher object, the event to act on, and the action to be executed on that event. Although it seems to be overkill, I’ll provide a third function, Register-FSWatcherEventHandler, that helps to define a proper event action for a file system watcher:

function Register-FSWatcherEventHandler
{
    <#
    .SYNOPSIS
        Registers a FileSystemWatcher event handler.

    .DESCRIPTION
        Specifies what is done when a file is changed, created, deleted, or renamed.

    .PARAMETER FileSystemWatcher
        Specifies a FileSystemWatcher object

    .PARAMETER EventName
        Specifies the type of event. Valid events are Changed, Created, Deleted, Disposed, Error, and Renamed.

    .PARAMETER EventAction
        Specifies what is done.

    .EXAMPLE
        PS C:\Scripts> $fsw = New-FSWatcher -Path 'C:\Temp'
        PS C:\Scripts> Register-FSWatcherEventHandler $fsw 'Created' -Action {
        >> Remove-Item $($eventArgs.FullPath) -Confirm
        }

        These two commands initialize a FileSystemWather for C:\Temp and registers an event handler which will offer to delete any created file.

    .LINK
        New-FSWatcher
        Start-FSWatcher
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [Alias("FSW")]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileSystemWatcher]
        $FileSystemWatcher
        ,
        [Parameter(Mandatory=$true, Position=1)]
        [ValidateSet('Changed', 'Created', 'Deleted', 'Disposed', 'Error', 'Renamed')]
        [String]
        $EventName
        ,
        [Parameter(Mandatory=$true, Position=2)]
        [Alias('Action')]
        [Scriptblock]
        $EventAction
    )

    if ($PSCmdlet.ShouldProcess("$($FileSystemWatcher.Path)", "Register Event Handler (for File $EventName)"))
    {
        Register-ObjectEvent -InputObject $FileSystemWatcher -EventName $EventName -Action $EventAction
    }
}

Have fun! :-)

Pit

PowerShell Proxy Extensions (Beta)

A few moments ago, at the PowerShell Deep Dive in Frankfurt, Kirk "Poshoholic" Munro and Shay Levy published the Beta of PSPXPowerShell Proxy Extensions Module (Project at CodePlex).

PSPX leverages so-called Proxy Functions in order to extend the functionality of native PowerShell Cmdlets. What is a proxy function? Basically, a proxy function is a wrapper around a Cmdlet. A proxy function has access to the Cmdlet’s parameters and has control over the command steppable pipeline. Implementing a proxy function is not a piece of cake. Earlier this year, Shay wrote about proxy functions as a guest blogger in the Hey, Scripting Guy! blog. It’s a great introduction.

With the PSPX you don’t need to care about that stuff behind the scenes. PSPX encapsulates complexity and therefore makes it very easy to extend PowerShell cmdlets.

Example: in PowerShell 1.0 and 2.0 the Get-ChildItem doesn’t come with a -Container switch meaning that if you want to get only Container objects (for example Directories/Folders in a file system) you need to pipe Get-ChildItem’s results to Where-Object {$_.PSIsContainer}. Wouldn’t it be nice if the Get-ChildItem had a -Container switch? With, PSPX you can fix Get-ChildItem on your own – and it’s very easy:

Fix-It Get-ChildItem `
    -Parameter @{
        Name = 'Container'
        PostProcess = {
            Where-Object {$_.PSIsContainer}
        }
    } `
    -DefineNow

This is just a simple example. Download PSPX, it’s an MSI file, from the CodePlex, install it, load the pspx module, and play with it… ;-)

PowerShell Function to Set Page File Initial/Maximum Size

The PowerShell 2.0 function below, Set-PageFile, sets a page file to the given initial and maximum size.

At first sight this seems to be an easy task – just set the properties InitialSize and MaximumSize accordingly (Win32_PageFileSetting class). The tricky part is that the system won’t let you change any page file setting as long as the system automatically manages these settings. My function takes care of this fact and disables the Win32_ComputerSystem‘s AutomaticManagedPagefile property if necessary.

The function supports PowerShell’s -WhatIf switch. Therefore you can test it safely without any impact.

function Set-PageFile
{
    <#
    .SYNOPSIS
        Sets Page File to custom size

    .DESCRIPTION
        Applies the given values for initial and maximum page file size.

    .PARAMETER Path
        The page file's fully qualified file name (such as C:\pagefile.sys)

    .PARAMETER InitialSize
        The page file's initial size [MB]

    .PARAMETER MaximumSize
        The page file's maximum size [MB]

    .EXAMPLE
        C:\PS> Set-PageFile "C:\pagefile.sys" 4096 6144
    #>

    [CmdletBinding(SupportsShouldProcess=$True)]
    param (
        [Parameter(Mandatory=$true,Position=0)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path,
        [Parameter(Mandatory=$true,Position=1)]
        [ValidateNotNullOrEmpty()]
        [Int]
        $InitialSize,
        [Parameter(Mandatory=$true,Position=2)]
        [ValidateNotNullOrEmpty()]
        [Int]
        $MaximumSize
    )

    Set-PSDebug -Strict

    $ComputerSystem = $null
    $CurrentPageFile = $null
    $Modified = $false

    # Disables automatically managed page file setting first
    $ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem -EnableAllPrivileges
    if ($ComputerSystem.AutomaticManagedPagefile)
    {
        $ComputerSystem.AutomaticManagedPagefile = $false
        if ($PSCmdlet.ShouldProcess("$($ComputerSystem.Path.Server)", "Disable automatic managed page file"))
        {
            $ComputerSystem.Put()
        }
    }

    $CurrentPageFile = Get-WmiObject -Class Win32_PageFileSetting
    if ($CurrentPageFile.Name -eq $Path)
    {
        # Keeps the existing page file
        if ($CurrentPageFile.InitialSize -ne $InitialSize)
        {
            $CurrentPageFile.InitialSize = $InitialSize
            $Modified = $true
        }
        if ($CurrentPageFile.MaximumSize -ne $MaximumSize)
        {
            $CurrentPageFile.MaximumSize = $MaximumSize
            $Modified = $true
        }
        if ($Modified)
        {
            if ($PSCmdlet.ShouldProcess("Page file $Path", "Set initial size to $InitialSize and maximum size to $MaximumSize"))
            {
                $CurrentPageFile.Put()
            }
        }
    }
    else
    {
        # Creates a new page file
        if ($PSCmdlet.ShouldProcess("Page file $($CurrentPageFile.Name)", "Delete old page file"))
        {
            $CurrentPageFile.Delete()
        }
        if ($PSCmdlet.ShouldProcess("Page file $Path", "Set initial size to $InitialSize and maximum size to $MaximumSize"))
        {
            Set-WmiInstance -Class Win32_PageFileSetting -Arguments @{Name=$Path; InitialSize = $InitialSize; MaximumSize = $MaximumSize}
        }
    }
}

Function to Bulk-Register PowerShell Snap-Ins

I wrote the PowerShell function below, Register-PSSnapin, to facilitate the usage of the InstallUtil.exe program in order to register a series of PowerShell snap-ins. Since this utility isn’t located in the  normal  command path, you have to find it in the .NET Framework’s directory. Or, with my function you just don’t care ;-)

Side note: in PowerShell 2.0 the concept of snap-ins is substantially replaced by binary modules. Apart from the fact that the core of PowerShell 2.0 is delivered as snap-ins, many software vendors still provide a PowerShell snap-in to allow for command-line based administration and automation. (For example the latest release of Citrix Provisioning Services 6.0 ships a PowerShell Snap-in.)

<#
.SYNOPSIS
Registers PowerShell Snap-ins.

.DESCRIPTION
Uses the InstallUtil tool in the .NET Framework to register one ore more snap-ins

.PARAMETER Path
Specifies the path to the file name or "module name" of the snap-in

.EXAMPLE
C:\PS> Register-PSSnapin -Path C:\Dev\Management\ManagementCmdlets.dll
#>
function Register-PSSnapin
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $Path
    )

    $CurrentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
    if (!($CurrentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)))
    {
        Write-Host -ForegroundColor Red 'This script requires elevated permission.'
        Exit
    }
    $InstallUtil = Join-Path (Split-path ([Object].Assembly.Location) -Parent) InstallUtil.exe
    foreach ($PathElement in $Path)
    {
        if (Test-Path -Path $PathElement)
        {
            if ($PSCmdlet.ShouldProcess("$PathElement", 'Register PowerShell Snap-in'))
            {
                Start-Process -FilePath $InstallUtil -ArgumentList $PathElement -Wait
            }
        }
        else
        {
            Write-Host "File not found - $PathElement"
        }
    }
}

Can I use the XenApp 6 Migration Tool on XenApp 6.5?

The XenApp 6 Migration Tool is a PowerShell 2.0 Module that pulls data from a MFCOM-based XenApp farm and adds it to a XenApp 6 server farm. As XenApp 6.5 is around now, including almost the same set of Cmdlets like its predecessor, it seems to be perfectly possible to use that Tool to migrate from XenApp 5 to XenApp 6.5 for example. So what’s the catch?

The answer is pretty simple: Citrix XenApp 6.5 has built-in migration capabilities which includes the former Migration Tool cmdlets. Therefore, don’t care about the past and take a closer look at the XenApp 6.5 Migration Center ;-)

The Migration Center is a PowerShell module and you can use it through a GUI or a command-line.

Citrix XenApp 6.0 to 6.5 Upgrade Script/Utility

Let’s say you have to upgrade Citrix XenApp 6.0 to 6.5. Since the XenApp 6.5 installer doesn’t include an upgrade path from 6.0 you would end up uninstalling all 6.0 components in correct order and finally installing XenApp 6.5 – manually. No better way? Yes, there seems to be a better way with a neat PowerShell script from Citrix.

The script XenAppUpgrade.ps1 (download) performs what you normally would do manually in order to upgrade a XenApp server from 6.0 to 6.5

The Citrix Support document CTX130614 outlines how to install, to prepare, and to use the script.

Citrix Web Interface 5.x vs. Citrix Presentation Server 4.0

These days, I had to introduce Citrix Web Interface (WI) in an older Terminal Server environment that consists of Microsoft Windows Server 2003 and Citrix Presentation Server (PS) 4.0. At first I considered this an easy task without any issues. Just install the latest WI available, configure a Web Site and a PNAgent Site, show functionality, and done. Far from it! Testing application launches via the freshly installed and properly configured WI I got error messages saying that a connection to the resource couldn’t be established. The message didn’t provide an error number or something unique.

The cause was a setting in WebInterface.conf that is turned On by default: RequireLaunchReference. Ensure that this parameter is set to Off in order to disable launch references.

Launch references are required for pass-through authentication to XenApp VM hosted apps for example. To use launch references, all the servers in the specified farm must run XenDesktop or PS 4.5 or later.

WMF… A Growing Management ECO System

At the Windows BUILD Developer Conference, this talk was definitely one of the best: Make your product manageable (Jeffrey Snover and Refaat Issa speaking)

[...] learn how to make your product manageable in standard and powerful ways using the Windows Management Framework (WMF) and plug into the growing ecosystem of tools and IT management capabilities. See how the WMF enables (via .NET, Native Code, PowerShell, Workflow and CIM/WSMan) managing your product by standard-based tools, powerful command utilities, REST APIs or standard Windows graphical user interface.

Windows Management Framework 3.0 – Community Technology Preview 1

The WMF 3.0 CTP1 for Windows 7 SP1 and Windows Server 2008 R2 SP1 (download) is out (was published yesterday) and brings updated management functionality. It contains Windows PowerShell 3.0 and new versions of WMI and WinRM. Please note that a CTP isn’t a final release but a preview release. It is designed to enable the community to experience and review the preliminary designs and direction of key features and to solicit feedback before features are finalized.

Some of the new features in Windows PowerShell 3.0 enable Service Providers to automatically build, maintain, and administer IT Infrastructures. With WMF 3.0 including PowerShell 3.0, WMI, and WinRM, Microsoft addresses the increasing need for automation driven by the era of Cloud Computing.