Friday Fun: Edit Recent File

As you might imagine I work on a lot of PowerShell projects at the same time. Sometimes I’ll start something at the beginning of the week and then need to come back to it at the end of the week. The problem is that I can’t always remembered what I called the file. So I end up listing files, sorting by last write time and then looking at the last 10 or so. Time to be more efficient so I came up with a little PowerShell v3 function called Edit-RecentFile.


#requires -version 3.0

Function Edit-RecentFile {

<# .Synopsis Open selected files for editing in the PowerShell ISE .Description This command will list the most recently modified files in the specified directory. By default the command will list the 25 most recently modified files. Using Out-Gridview as an interface, you can select the files you wish to edit and they will open in the PowerShell ISE. If you are already in the ISE then each file will open in a new tab. This command requires PowerShell v3. .Example PS C:\> edit-recentfile d:\myscripts -last 10
.Example
PS C:\> edit-recentfile c:\work\*.txt
#>

[cmdletbinding()]
Param(
[Parameter(Position=0)]
[ValidateScript({Test-Path $_})]
[string]$Path="c:\scripts",
[Parameter(Position=1)]
[ValidateNotNullorEmpty()]
[int]$Last=25,
[switch]$Recurse
)

Write-Verbose "Getting $last recent files from $path."

#get last X number of files from the path and pipe to Out-Gridview
$files = Get-ChildItem -Path $Path -file -Recurse:$Recurse |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First $Last -Property FullName,LastWriteTime,Length,Extension |
Out-GridView -Title "Select one or more files to edit and click OK" -PassThru

#if files were selected, open them in the ISE
if ($files) {
Write-Verbose "Opening`n$($files.fullname | out-string)"
#if in the ISE use PSEDIT
if ($host.name -match "ISE") {
Write-Verbose "Detected the PowerShell ISE"
psedit $files.Fullname
}
else {
#otherwise assume we're in the console
Write-Verbose "Defaulting to PowerShell console"
ise ($files.FullName -join ",")
}
} #if $files

} #close Edit-RecentFile

The core section gets the directory listing of the most recently modified files but then pipes them to Out-Gridview.

Edit-RecentFile

In PowerShell v3, Out-Gridview has been extended so that you can select one or more items and pass them back to the pipeline.


$files = Get-ChildItem -Path $Path -file -Recurse:$Recurse |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First $Last -Property FullName,LastWriteTime,Length,Extension |
Out-GridView -Title "Select one or more files to edit and click OK" -PassThru

Once I have the collection of files, I can use the FullName property and open them in the PowerShell ISE. From the console I can use the ise alias and pass a comma separated list of files. The fun part is turning the array of file paths into a comma separated string. But that is easily done using the -Join operator.


ise ($files.FullName -join ",")

If I am in the PowerShell ISE already, I can do a similar thing using the PSEDIT command, although this takes the array without any reformatting.


if ($host.name -match "ISE") {
Write-Verbose "Detected the PowerShell ISE"
psedit $files.Fullname
}

The function has a default path set to C:\Scripts which you might want to change. By default the function will only look in the root of the folder, but you can use -Recurse. The download file will also create an alias erf, for the function.

Download Edit-RecentFile and let me know how it works out for you.

Friday Fun: A GridView Drive Report

I’ve been experimenting with different techniques to work with PowerShell in graphical ways, but without resorting to complex solutions such as WinForms or ShowUI. For today’s Friday Fun I have a little script that presents a drive usage report using WMI and Out-GridView. As always, my goal with these articles is to impart a nugget of useful information, regardless of whether you need the complete solution.

Getting the drive data with WMI is pretty straightforward.

But what I want is something I can send to Out-Gridview that will give me a graphical representation of drive utilization. So I’ll take this basic command and pipe it to Select-Object, and add a few custom properties.

My custom properties reformat values into MB and a Free percentage. These values are formatted as numbers so they can be sorted. That much would be fine if you wanted to write that to the pipeline. But I’m going to get graphical so I also define a property called FreeGraph. The value is simply the “|” character displayed once for each percent of free space. Sure, I could write this to the pipeline and see something like this:

But where this gets really interesting is where I pipe $data to Out-GridView. Here’s an example where I queried several computers.

Now I have a graphical report that I can filter and sort. You may have to manually resize the display and adjust columns but you get the idea. But as they say on late night TV, wait there’s more.

If you have PowerShell v3, Out-Gridview now supports passthru. You can select one or more objects in Out-GridView and they will be written to the pipeline. This leads to some interesting opportunities. Here’s a variation that opens the selected drives in a new gridview window, displaying all the WMI properties.

This gridview allows me to select multiple entries.

When I click OK, the objects are piped back to PowerShell and in this case back to Out-Gridview.

Or, maybe you simply need to open the drive on the remote machine. Since I’m querying logical disks, they should each have an administrative share which I can construct from the drive object, and then open using Invoke-Item.

In these examples Out-Gridview is set to allow multiple selections. If you prefer to limit selection to one object, then use -Outputmode Single in place of -Passthru.

I’ve put all of these commands in a script you can download and try for yourself.

PowerShell Hyper-V Memory Report

Since moving to Windows 8, I’ve continued exploring all the possibilities around Hyper-V on the client, especially using PowerShell. Because I’m trying to run as many virtual machines on my laptop as I can, memory considerations are paramount as I only have 8GB to work with. Actually less since I still have to run Windows 8!

Anyway, I need to be able to see how much memory my virtual machines are using. The Get-VM cmdlet can show me some of the data.

Actually, there are more properties I can get as well.

Those values are in bytes so I would need to reformat them to get them into something more meaningful like bytes. Not especially difficult, but not something I want to have to type all the time. Now, I can also get memory information with Get-VMMemory and this is formatted a little nicer.

What I like about this cmdlet is that it also shows the buffer and priority settings.

In the end, I decided the best course of action was to build my own function that combined information from both cmdlets. The result is a custom object that gives me a good picture of memory configuration and current use. The function, Get-VMMemoryReport, is part of a larger HyperV toolkit module I’m developing but I thought I’d share this with you now.

I wrote the function with the assumption of piping Hyper-V virtual machines to it. Although I can also pipe names to it and the function will then get the virtual machine.

Once the function has the virtual machine object, it also gets data from Get-VMMemory.

Finally, it creates a hash table using the new [ordered] attribute so that the key names will be displayed in the order I enter them. I use this hash table to write a custom object to the pipeline. I could have used the new [pscustomobject] attribute as well, but I felt in a script using New-Object was a bit more meaningful. With this command, I get output like this:

Or I can explore the data in other ways. I can create an HTML report, export to a CSV or take advantage of Out-GridView.

Here’s the report for my currently running virtual machines.

The function defaults to connecting to the localhost, but I am assuming that if you have an Hyper-V server you could use this from any system that has they Hyper-V module also installed. I don’t have a dedicated Hyper-V server to test with so maybe someone will confirm this for me.

In the meantime, download Get-VMMemoryReport and let me know what you think.

Friday Fun: Get Latest PowerShell Scripts

Probably like many of you I keep almost all of my scripts in a single location. I’m also usually working on multiple items at the same time. Some times I have difficult remembering the name of a script I might have been working on a few days ago that I need to return to. The concept is simple enough: search my script directory for PowerShell files sorted by the last write time and look for the file I need.

That’s a lot to type so why not build a function to do the work for me? In fact, since I spend a lot of time in the PowerShell ISE, why not produce graphical output? The easiest way is to pipe results to Out-Gridview. So after a little tinkering, I came up with Get-LatestScript.


Function Get-LatestScript {

[cmdletbinding()]

Param(
[Parameter(Position=0)]
[ValidateScript({Test-Path $_})]
[string]$Path=$global:ScriptPath,
[Parameter(Position=1)]
[ValidateScript({$_ -ge 1})]
[int]$Newest=10
)

if (-Not $path) {
$Path=(Get-Location).Path
}

#define a list of file extensions
$include="*.ps1","*.psd1","*.psm1","*.ps1xml"

Write-Verbose ("Getting {0} PowerShell files from {1}" -f $newest,$path)

#construct a title for Out-GridView
$Title=("Recent PowerShell Files in {0}" -f $path.ToUpper())

Get-ChildItem -Path $Path -Include $include -Recurse |
Sort-Object -Property lastWriteTime -Descending |
Select-Object -First $newest -Property LastWriteTime,CreationTime,
@{Name="Size";Expression={$_.length}},
@{Name="Lines";Expression={(Get-Content $_.Fullname | Measure-object -line).Lines}},
Directory,Name,FullName |
Out-Gridview -Title $Title

}

I decided to try something different with the Path variable. I set the default to a global variable, ScriptPath. The idea is that in your PowerShell profile, you’ll have a line like this:


$ScriptPath="C:\Scripts"

If the function finds this variable, it will use it. Otherwise it will use the current location. Notice I’m also using a validation attribute to verify the path. By default the function returns the 10 newest PowerShell files, based on the last write time. The number of files can be controlled by the -Newest parameter.

In the heart of the script is a one-line expression to find all matching files in the script folder and subfolders.


Get-ChildItem -Path $Path -Include $include -Recurse |
Sort-Object -Property lastWriteTime -Descending

These files are then sorted on the LastWriteTime in descending order and then I only select the first $newest number of files.


| Sort-Object -Property lastWriteTime -Descending |
Select-Object -First $newest ...

I am only interested in a few file properties so I select them. I also add a custom property to measure the file and get the number of lines in the script.


...-Property LastWriteTime,CreationTime,
@{Name="Size";Expression={$_.length}},
@{Name="Lines";Expression={(Get-Content $_.Fullname | Measure-object -line).Lines}},
Directory,Name,FullName

Finally the results are piped to Out-Gridview.


... | Out-Gridview -Title $Title

For now, I have to manually open the file. Perhaps I’ll create a WinForm or use ShowUI to integrate it into the PowerShell ISE.

You can download Get-LatestScript which includes comment based help.

Friday Fun: PowerShell ISE Function Finder

At the PowerShell Deep Dive in San Diego, I did a lightning session showing off something I had been working on. Sometimes I don’t know what possesses me, but I felt the need for a better way to navigate my PowerShell scripts files that had many functions. Some files, especially modules, can get quite long and contain a number of functions. When using the PowerShell ISE I wanted a faster way to jump to a function. The problem is I don’t always remember what I called a function or where it is in the file. It is very easy to jump to a particular line in the ISE using the Ctrl+G shortcut.

So I started with some basics.


$Path=$psise.CurrentFile.FullPath

I decided I’d use a regular expression pattern to find my functions. I write my functions like this:


Function Get-Foo {

Param()
...

So I came up with a regex pattern to match the first line and to include the Filter keyword as well.


[regex]$r="^(Function|Filter)\s\S+\s{"

I first thought of searching the content of the current file, but that won’t give me a line number. Then I thought of Select-String. With my regex pattern, I can get the content of the current file and pipe it to Select-String. The match object that comes out the other end of the pipeline includes a line number property (awesome) and the matching line. I decided to do a little string parsing on the later to drop off the trailing curly brace.


$list=get-content $path |
select-string $r | Select LineNumber,
@{Name="Function";Expression={$_.Line.Split()[1]}}

Because I’m in the ISE I felt the need to stay graphical, so my first thought was to pipe the results to Out-Gridview.


$list | out-gridview -Title $psise.CurrentFile.FullPath

Here’s a sample result.

Now I can see the function name and line number. In the ISE I can do Ctrl+G and jump to the function. Of course, if I modify the file and line numbers change I need to close the grid and re-run my command. But wait, there’s more….

I’ve never done much with the WPF and figured this would be a great opportunity to do something with the ShowUI module. I already had the data. All I had to do was create a form with ShowUI. This is what I ended up with.


[string]$n=$psise.CurrentFile.DisplayName
ScrollViewer -ControlName $n -CanContentScroll -tag $psise.CurrentFile.FullPath -content {
StackPanel -MinWidth 300 -MaxHeight 250 `
-orientation Vertical -Children {
#get longest number if more than one function is found
if ($count -eq 1) {
[string]$i=$list.Linenumber
}
else {
[string]$i=$list[-1].Linenumber
}
$l=$i.length
foreach ($item in $list) {

[string]$text="{0:d$l} {1}" -f $item.linenumber,$item.function

Button $text -HorizontalContentAlignment Left -On_Click {
#get the line number
[regex]$num="^\d+"
#parse out the line number
$goto = $num.match($this.content).value
#grab the file name from the tab value of the parent control
[string]$f= $parent | get-uivalue
#Open the file in the editor
psedit $f
#goto the selected line
$psise.CurrentFile.Editor.SetCaretPosition($goto,1)
#close the control
Get-ParentControl | Set-UIValue -PassThru |Close-Control
} #onclick
} #foreach
}
} -show

The result is a stack panel of buttons in a scroll control. The button shows the line number and function name.

When a button is clicked, the function gets the line number and automatically jumps to it. Originally I was leaving the control open, but this means the function is still running. And if I change the script the line numbers are off so I simply close the form after jumping to the function.

In the end, I packaged all of this as a script file that adds a menu choice. If ShowUI is available, the function will use it. Otherwise the function defaults to Out-GridView.


Function Get-ISEFunction {

[cmdletbinding()]
Param([string]$Path=$psise.CurrentFile.FullPath)

#import ShowUI if found and use it later in the functoin
if (Get-module -name ShowUI -listavailable) {
Import-Module ShowUI
$showui=$True
}
else {
Write-Verbose "Using Out-GridView"
$showui=$False
}

#define a regex to find "function | filter NAME {"
[regex]$r="^(Function|Filter)\s\S+\s{"

$list=get-content $path |
select-string $r | Select LineNumber,
@{Name="Function";Expression={$_.Line.Split()[1]}}

#were any functions found?
if ($list) {
$count=$list | measure-object | Select-object -ExpandProperty Count
Write-Verbose "Found $count functions"
if ($showui) {
<# display function list with a WPF Form from ShowUI Include file name so the right tab can get selected #>

[string]$n=$psise.CurrentFile.DisplayName
Write-Verbose "Building list for $n"

ScrollViewer -ControlName $n -CanContentScroll -tag $psise.CurrentFile.FullPath -content {
StackPanel -MinWidth 300 -MaxHeight 250 `
-orientation Vertical -Children {
#get longest number if more than one function is found
if ($count -eq 1) {
[string]$i=$list.Linenumber
}
else {
[string]$i=$list[-1].Linenumber
}
$l=$i.length
foreach ($item in $list) {

[string]$text="{0:d$l} {1}" -f $item.linenumber,$item.function
Write-Verbose $text
Button $text -HorizontalContentAlignment Left -On_Click {
#get the line number
[regex]$num="^\d+"
#parse out the line number
$goto = $num.match($this.content).value
#grab the file name from the tab value of the parent control
[string]$f= $parent | get-uivalue
#Open the file in the editor
psedit $f
#goto the selected line
$psise.CurrentFile.Editor.SetCaretPosition($goto,1)
#close the control
Get-ParentControl | Set-UIValue -PassThru |Close-Control
} #onclick
} #foreach
}
} -show

} #if $showui
else {
#no ShowUI module so use Out-GridView
$list | out-gridview -Title $psise.CurrentFile.FullPath
}
}
else {
Write-Host "No functions found in $($psise.CurrentFile.FullPath)" -ForegroundColor Magenta
}

} #close function

#Add to the Add-ons menu
$PSISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("List Functions",{Get-ISEFunction},$null)

#optional alias
set-alias gif get-isefunction

Now, I can click the List Functions menu choice and I’ll get a graphical list of any functions in the current file. I’m sure the regex could be tweaked. I’m also sure there are improvements I could make to the ShowUI code, but it works.

Download Get-ISEFunction and let me know what you think.