Tag Archives: ISE

Create PowerShell Scripts with a Single Command

One of the drawbacks to using a PowerShell script or function is that you have to write it. For many IT Pros, especially those new to PowerShell, it can be difficult to know where to start. I think more people would write their own tools if there was an easy way to automatically write them. So here’s my solution. I wrote a function called New-PSCommand that is intended as a rapid development tool for a PowerShell advanced function. It should work in either PowerShell v2 or v3.

Here’s the function, although I’m not going to spend much time explaining how it works but rather how to use it.


Function New-PSCommand {
#comment base help goes here

[cmdletbinding()]

Param(
[Parameter(Mandatory=$True,HelpMessage="Enter the name of your new command")]
[ValidateNotNullorEmpty()]
[string]$Name,
[ValidateScript({
#test if using a hashtable or a v3 [ordered] hash table
($_ -is [hashtable]) -OR ($_ -is [System.Collections.Specialized.OrderedDictionary])
})]

[Alias("Parameters")]
[object]$NewParameters,
[switch]$ShouldProcess,
[string]$Synopsis,
[string]$Description,
[string]$BeginCode,
[string]$ProcessCode,
[string]$EndCode,
[switch]$UseISE
)

Write-Verbose "Starting $($myinvocation.mycommand)"
#add parameters
$myparams=""
$helpparams=""

Write-Verbose "Processing parameter names"

foreach ($k in $newparameters.keys) {
Write-Verbose " $k"
$paramsettings = $NewParameters.item($k)

#process any remaining elements from the hashtable value
#@{ParamName="type[]",Mandatory,ValuefromPipeline,ValuefromPipelinebyPropertyName,Position}

if ($paramsettings.count -gt 1) {
$paramtype=$paramsettings[0]
if ($paramsettings[1] -is [object]) {
$Mandatory = "Mandatory=`${0}," -f $paramsettings[1]
Write-Verbose $Mandatory
}
if ($paramsettings[2] -is [object]) {
$PipelineValue = "ValueFromPipeline=`${0}," -f $paramsettings[2]
Write-Verbose $PipelineValue
}
if ($paramsettings[3] -is [object]) {
$PipelineName = "ValueFromPipelineByPropertyName=`${0}" -f $paramsettings[3]
Write-Verbose $PipelineName
}
if ($paramsettings[4] -is [object]) {
$Position = "Position={0}," -f $paramsettings[4]
Write-Verbose $Position
}
}
else {
#the only hash key is the parameter type
$paramtype=$paramsettings
}

$item = "[Parameter({0}{1}{2}{3})]`n" -f $Position,$Mandatory,$PipelineValue,$PipelineName
$item +="[{0}]`${1}" -f $paramtype,$k
Write-Verbose "Adding $item to myparams"
$myparams+="$item, `n"
$helpparams+=".PARAMETER {0} `n`n" -f $k
#clear variables but ignore errors for those that don't exist
Clear-Variable "Position","Mandatory","PipelineValue","PipelineName","ParamSettings" -ErrorAction SilentlyContinue

} #foreach hash key

#get trailing comma and remove it
$myparams=$myparams.Remove($myparams.lastIndexOf(","))

Write-Verbose "Building text"
$text=@"
Function $name {
<#
.SYNOPSIS
$Synopsis

.DESCRIPTION
$Description

$HelpParams
.EXAMPLE
PS C:\> $Name

.NOTES
Version: 0.1
Author : $env:username

.INPUTS

.OUTPUTS

.LINK
#>

[cmdletbinding(SupportsShouldProcess=`$$ShouldProcess)]

Param (
$MyParams
)

Begin {
Write-Verbose "Starting `$(`$myinvocation.mycommand)"
$BeginCode
} #begin

Process {
$ProcessCode
} #process

End {
$EndCode
Write-Verbose "Ending `$(`$myinvocation.mycommand)"
} #end

} #end $name function

"@

if ($UseISE -and $psise) {
$newfile=$psise.CurrentPowerShellTab.Files.Add()
$newfile.Editor.InsertText($Text)
}
else {
Write $Text
}

Write-Verbose "Ending $($myinvocation.mycommand)"

} #end New-PSCommand function

The premise of this function is to take a hash table of parameter definitions and create a new PowerShell advanced function.

The hash table key is the name of your parameter and the key is its type. The only other value you need is the name for your new function. The New-PSCommand function creates a new advanced function, complete with comment-based help, and writes the text to the pipeline. That way you can either review it, pipe it to Out-File or copy it to the clipboard.


PS C:\>$paramhash=@{Name="string[]";Test="switch";Path="string"}
PS C:\> New-PSCommand -name "Set-MyScript" -Newparameters $paramhash | out-file "c:\scripts\set-myscript.ps1"

The hash table of parameters for my Set-MyScript function includes an array of strings for $Name, a string for $Path, and a switch for $Test. Here’s the output:


Function Set-MyScript {
<#
.SYNOPSIS

.DESCRIPTION

.PARAMETER Path

.PARAMETER Name

.PARAMETER Test

.EXAMPLE
PS C:\> Set-MyScript

.NOTES
Version: 0.1
Author : Jeff

.INPUTS

.OUTPUTS

.LINK
#>

[cmdletbinding(SupportsShouldProcess=$False)]

Param (
[Parameter()]
[string]$Path,
[Parameter()]
[string[]]$Name,
[Parameter()]
[switch]$Test
)

Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"

} #begin

Process {

} #process

End {

Write-Verbose "Ending $($myinvocation.mycommand)"
} #end

} #end Set-MyScript function

All you need to do is fill in the script with the code you want to run. New-PSCommand does all of the grunt work for you leaving you just the fun part. I also support an alternate hashtable setup if you want to specify some parameter attributes. Create a hash table using this format:


@{ParamName="type[]",Mandatory,ValuefromPipeline,ValuefromPipelinebyPropertyName,Position}

Here’s an example:

$h = @{Name="string[]",$True,$True,$False,0;
Path="string",$false,$false,$false,1;
Size="int",$false,$false,$true;
Recurse="switch"
}

I also let you specify commands to use in the Begin, Process and End scriptblocks. You can also define values for the help synopsis and description.

The last little bell on this tool is that if you run it in the PowerShell ISE, you can use the -UseISE switch which will open your new script in a new file in the ISE. This means you could open a new PowerShell tab in the ISE and run commands like this:


$hash = [ordered]@{
Name="string[]",$True,$True,$False,0
Path="string",$false,$false,$false,1
PixieDust="int",$false,$false,$true
NoUnicorn="switch"
}

$begin={
#initialize some variables
$arr=@()
$Love=$True
$ring=1
}

$end="write-host 'Finished' -foreground Green"
$synopsis = "Get magical user data"
$desc = @"
This command will do something really amazing and magical. All you need to do is provide
the right amount of pixie dust and shavings from a unicorn horn.

This requires PowerShell v4 and a full moon.
"@

. C:\scripts\New-PSCommand.ps1
New-PSCommand -Name Get-UserData -NewParameters $hash -BeginCode $begin -EndCode $end -Synopsis $synopsis -Description $desc -UseISE

By the way, I’m running PowerShell v3 so I can use a [ordered] hash table which I encourage you to use if you can. When executed, I get a new script in the ISE ready for me to finish.


Function Get-UserData {
<#
.SYNOPSIS
Get magical user data

.DESCRIPTION
This command will do something really amazing and magical. All you need to do is provide
the right amount of pixie dust and shavings from a unicorn horn.

This requires PowerShell v4 and a full moon.

.PARAMETER Name

.PARAMETER Path

.PARAMETER PixieDust

.PARAMETER NoUnicorn

.EXAMPLE
PS C:\> Get-UserData

.NOTES
Version: 0.1
Author : Jeff

.INPUTS

.OUTPUTS

.LINK
#>

[cmdletbinding(SupportsShouldProcess=$False)]

Param (
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$False)]
[string[]]$Name,
[Parameter(Position=1,Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelineByPropertyName=$False)]
[string]$Path,
[Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelineByPropertyName=$True)]
[int]$PixieDust,
[Parameter()]
[switch]$NoUnicorn
)

Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"

#initialize some variables
$arr=@()
$Love=$True
$ring=1

} #begin

Process {

} #process

End {
write-host 'Finished' -foreground Green
Write-Verbose "Ending $($myinvocation.mycommand)"
} #end

} #end Get-UserData function

I hope that a tool like this helps cut down on your development time. Please download New-PSCommand and let me know what you think.

Friday Fun: Save All Open PowerShell ISE Files

Here’s a little tidbit that I previously shared on Twitter and Google Plus. The PowerShell ISE obviously has a Save menu choice. But there’s no menu option to save all open files. But you can add one yourself. All of the open files are part of the $psise.CurrentPowerShellTab.Files collection. Each item has a Save() method so to save all the files all you need to do is enumerate the collection ForEach and invoke the Save() method. You could run a command like this in the command prompt of the ISE.


$psise.CurrentPowerShellTab.files | foreach {
$_.Save()
}

Where you will run into problems is with files that have not been saved yet and are still untitled. This code will throw exceptions. The solution is to simply skip untitled files.


$psise.CurrentPowerShellTab.files |
where {-Not ($_.IsUntitled)} |
foreach {
$_.Save()
}

You can save untitled files with the SaveAs() method but you have to provide a file name. I can’t find anyway to easily invoke the GUI prompt so for now I have manually save untitled files.

Now, instead of running this command every time, let’s add it to the ISE Add-Ons menu.


$sb={
$psise.CurrentPowerShellTab.files |
where {-Not ($_.IsUntitled)} |
foreach {
$_.Save()
}
}

$psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Save All Files",$sb,"Ctrl+Shift+A") | out-null

I even added a keyboard shortcut. Add these lines to your PowerShell ISE profile and you’ll always have this menu option. Or download Add-SaveAllISE.ps1 and load the script in your profile. As far as I can tell this works in both PowerShell v2 and v3.

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.

Embrace and Extend the PowerShell ISE

I did a presentation today for the PowerShell Virtual Chapter of SQLPass. The recording will be posted later and I’ll update this when I have the link. But in the meantime, as promised, I wanted to make available my slide deck and demos. You’ll need to download the individual ISE Addons from the respective sites. Links are in the slides. You can use the script demos as guides for your own ISE extensions.

Download a PDF version of Embrace and Extend the PowerShell ISE.
Download ISEAddonDemoFiles.zip with my demo scripts.

Convert Aliases with the Tokenizer

Last week I posted a function you can use in the Windows PowerShell ISE to convert aliases to command definitions. My script relied on regular expressions to seek out and replace aliases. A number of people asked me why I didn’t use the PowerShell tokenizer. My answer was that because I’m not a developer and don’t think that way. But after working a bit with it I can see the value so I have another function you can use in the ISE to convert aliases to commands. Continue reading

PowerShell ISE Convert All Aliases

Yesterday I posted an article on how to convert a selected word to an alias or cmdlet. While I think there is still some value in this piecemeal approach. sometimes you want to make wholesale changes, such as when troubleshooting a script that someone else wrote that is full of cryptic aliases. I have a function you can integrate into the ISE that will convert all aliases in a block of selected text and convert them to their full cmdlet name equivalents. Continue reading

PowerShell ISE Alias to Command

Earlier this week I posted a function that you could incorporate into the PowerShell ISE to convert selected text to upper or lower case. I was challenged to take this a step further and come up with a way to convert aliases to commands. Which is exactly what I did. Continue reading