Code Refactoring

ISESteroids helps you write PowerShell code fast. You can use predefined reusable code snippets, auto-create function bodies, and automatically insert error handlers. PSSharper constantly checks your code for errors, best-practice suggestions, and detects issues like misspelled or undefined variables.

Aside from helping you to compose PowerShell code quickly, you’ll also constantly learn new things as you go.

Advanced Code Snippets

Typing the same code statements over and over again takes time. ISESteroids supports real-time code snippet suggestions. When you type code, and the background of the current statement turns cyan, then you know there is a code snippet:

Snippet Available

Stop typing, and press TAB instead. ISESteroids inserts the appropriate code snippet:

Insert Snippet

Right after you inserted the snippet, the status bar shows multiple clickable links to manage the snippet:

Changing Snippets

  • “snippet content”: opens the Snippet Manager and lets you edit and change the snippet code
  • “keyboard shortcut”: opens the Snippet Manager, too. Click “Edit Metadata” to change the snippet shortcut that triggers insertion
  • “view all snippet shortcuts”: opens a GridView and lists all currently defined snippet shortcuts

Code snippet shortcuts work in the console pane, too. They are just not marked with a cyan background but if you know the keyboard shortcut and press TAB, snippets insert into the console just as they do in the editor pane.

Context Sensitivity and Multiple Edits

Note that snippet shortcuts may be context sensitive. When you enter “for” at the beginning of a line and press TAB to insert the snippet, a for loop is inserted. When you enter “for” following another command and press TAB, a foreach-object cmdlet is inserted.

Note also that some snippets support multiple edit. If you type “for” at the beginning of a line and press TAB, the for loop is not just inserted, but a number of fields are marked. You can continue to type to adjust the first field, then press TAB to move to the next field.

Snippet Customization

While in Snippet Input Mode, the status bar tells you what kind of adjustment can be made.

Some fields like the iterator variable have siblings, and when you change the variable, the variable changes in other places inside the snippet as well.

Click outside the snippet, or press ENTER, to end the snippet input mode.

Snippet Selector

Press CTRL+J to open the hierarchical snippet selector. It shows all available snippets, including those that have no keyboard shortcut.

Snippet Selector

When you hover over one of the snippets in the list, a tooltip displays the snippet code, and on the tooltip, you find clickable links to edit the snippet in the Snippet Editor. You can also create new folders in the snippet hierarchy, and new snippets. Click “New Folder” or “New Snippet” inside the selector.

Snippet Editor: Creating New Snippets

The snippet editor is a specialized editor designed to create advanced code snippets. You can open the editor for existing or new snippets. Let’s assume you often use collapsible regions and would like to create a snippet for this. You simply enter the code you want to turn into a snippet:


#region MyRegion

#CONTENT

#endregion MyRegion

Next, select the code, then click the “hammer” icon in the left editor bar, and choose “Create Code Snippet from Selection”:

Create New Snippet

This opens the Snippet Editor and inserts the selected code. Continue to edit the code until it looks like you want it to be. Right-click the code, and choose “Set Desired Final Caret Position”. This will later be the final caret position after the snippet was inserted.

Creating AutoFill Fields

This step is optional. If you want the user to fill out elements of your snippet after it was inserted, select these portions of your code, then click the button “Field”.

Define Fields

This brings up another dialog where you can define the field:

Define Field

  • Supports SurroundWith: only one field can have this option enabled. When you later select code and then insert this snippet, the selected code will be inserted into this field. Do not use this for the region name. Instead, create a second field for the code “# Content”, and select “Supports SurroundWith” for that field. This will enable you to surround selected code with your code region.
  • Show in Selection menu: when “Supports SurroundWith” is enabled, and you enable this option, then your snippet will show in the “Hammer” menu that appears in the left editor bar once you select code.

Click OK when you are done. Fields are marked with a thin line.

Sibling Fields

Your region has two identical texts “MyRegion”: one at the beginning, and one at the end. Click the button “Include Siblings” to auto-apply field content to any other portion of the code that has the same content.

This is what the Snippet Editor may look like after some editing:

Completed Snippet

  1. Defines the final caret position. Set or change by right-clicking into the editor.
  2. Defines an Auto-Fill field. Edit to right-click into the field.
  3. Defines a sibling. Automatically marked when you click into the parent field and the button “Include Siblings” is enabled.
  4. Defines another Auto-Fill field. This field should have the options “Supports SurroundWith” and “Show in Selection menu” enabled so that later selected code is inserted here.

When you are done, click OK. A final dialog opens and asks for some metadata. Enter at least a title. It is recommended that you also add a keyboard shortcut. Choose a keyboard shortcut that you would typically enter anyway, like “#region” in this case:

Snippet Metadata

Once you click OK, you are asked where to save the snippet, and the snippet is created. The default location for user defined snippets is the folder “Snippets” inside your “WindowsPowerShell” folder.

Using Your New Snippet

You can immediately use your new snippet!

When you enter your keyboard shortcut, its background turns cyan, and you can insert your snippet by pressing TAB. Since you defined auto-fill fields, the snippet enables input mode, and you can customize it.

Inserting New Snippet

Note how setting the start tag also sets the end tag because you defined sibling fields.

Next, select some existing code. Then click on the Hammer icon in the left editor margin, and choose “Surround With”. Your snippet is listed, and when you choose it, your selected code is surrounded by a new collapsible region. All you need to do is set the region name, and conveniently enough the end tag is again automatically set, too.

Snippet Compatibility

Snippets created by ISESteroids are compatible with the native ISE. In the native ISE, you can also press CTRL+J to open a snippet selector and insert your newly created snippets. The snippet selector in native ISE is one-dimensional, there are no snippet keyboard shortcuts, and no auto-fill fields, though.

Bulk (Variable) Rename

If you’d like to rename variables, or other items in your script, simply click the item (do not select any text), then press F2. All instances of the same kind are selected, and when you type, all related instances are renamed as well.

Variable Rename

  1. Click variable to rename, press F2, enter a new name
  2. All related variables are also selected and renamed
  3. All affected parameters are also selected and renamed

Selecting Text Does Simple Search&Replace

When you select text (rather than just clicking on some element), F2 performs a plain text search&replace. It then selects all text that matches your selected text.

While plain text search&replace can be useful, never use it to rename variables. As you have seen, renaming a variable has effects on parameters and other elements that a plain text search&replace would not find.


[/toggle]

Once you select code in ISESteroids, a “Hammer” icon appears in the left editor margin. Click it to view refactoring options.

Selection Tool

The list of refactoring options depends on the code you selected but would typically look similar to this:

Selection Choices

  • Surround with: shows a list of code snippets that support this feature. The snippet will be inserted, and the selected code is placed inside the snippet.
  • Comment out: Selected code is commented out (CTRL+SHIFT+B)
  • Align assignment operators: if selection contains at least two assignments, the assignment operators are aligned
  • Copy as HTML: Selection is converted to HTML and placed as text onto the clipboard. HTML can be used to display the selected code in high fidelity in blog posts and on websites
  • Convert Syntax to Function: if the selection resembles a valid syntax, defining a function with its parameters, then this will convert the syntax to the full PowerShell function code
  • Create Code Snippet from Selection: turns the selected code into a reusable code snippet (see section “Advanced Code Snippets”)
  • Auto-Create Function: takes the selected code, and wraps it into a function body. Variable assignments can be turned into function parameters.
  • Expand selection: selects the next higher PowerShell language structure (CTRL+Q)

PSSharper Real-Time Quality Analysis

PSSharper is a highly efficient code analysis engine working in the background as you code. It adds warnings and suggestions that show up in the PSSharper Bar at the bottom of the editor as well as through adornments and tooltips right in your code.

PSSharper Bar

  1. A ghosted variable, indicating that this variable is defined but never used anywhere
  2. A strike-through variable indicating that this assignment is invalid because the variable name is reserved by PowerShell
  3. A light bulb indicating auto-refactor options to fix the issue. Click the light-bulb to see options
  4. A textmarker background indicating that the variable is undefined and has no value
  5. A tooltip explaining the issue in more depth. Appears when you hover over an issue.
  6. A thin dotted green underline, indicating that there are refactoring options. Click the adornment to see a light-bulb in the editor margin
  7. A “close” button to slide the PSSharper Bar out of view. You get it back via menu “PSSharper / Show PSSharper Bar”
  8. PSSharper Bar reports the type and number of issues found. Click a button to open the PSSharper Addon with more details.

Set the Target PowerShell Version

One of the first things you ought to do is set the PowerShell version(s) you want to target. Many issues are version related, and many auto-fixes are applicable to certain PowerShell versions as well. So in order to not receive unnecessary issue warnings, choose “Compatibility / Set PowerShell Target”. A dialog opens. Set the desired target versions, and click OK.

Target PowerShell Version

Ad-Hoc Fixes

When you click an issue in your code, PSSharper frequently shows a yellow or red light bulb in the left editor margin, indicating there are automatic refactoring fixes available.

Light Bulb

  • There may be multiple fixable issues
  • All fixes are just suggestions. Apply fixes only if they make sense to you. You can always undo changes by pressing CTRL+Z.
  • Manage Issue: opens the PSSharper Addon, selects the issue in the issue list, and offers additional options (see below)

Press CTRL+ENTER

When you press CTRL+ENTER, PSSharper sets your caret to the next issue. If the caret is already on an issue, and there is a default auto-fix, CTRL+ENTER invokes it.

PSSharper AddOn Bar

When you click an issue group on the PSSharper Bar, or when you choose “Manage Issue” in the light bulb context menu of a particular issue, the PSSharper Addon opens. It lists all issues of a given category:

PSSharper Addon

  1. Currently selected targeted PowerShell versions. Click to change.
  2. Load and Save PSSharper configurations.
  3. Category Name of currently listed issues
  4. Rule, listing all issues of the same kind
  5. Rule Personalization: you can choose to ignore a particular issue type, or use different adornments
  6. Visual issue preview
  7. Click the magnifying icon to open a menu with options
  8. Fixes the currently selected issue (if automatic fix is available)
  9. Fixes all batch-enabled rules in entire script. Click on button #5 to batch-enable a particular rule.
  10. Exports all issues to a CSV file for external analysis and reportin (you can also run Get-PSSharperData)

Auto-Format

ISESteroids can apply a set of best practice formatting guidelines to a script. Often, when you receive code from other sources, this is a good start to formalize the script code so it becomes easier to read. Click the button for the Auto-Format Addon. Next, click “Fix Script Now”.

This will start auto-formatting the current script and may take a couple of seconds. In the console pane, the formatter reports the action it takes.

Auto Format

If you need more control over what gets formatted, or want to invoke only individual fixer units, click the tab “Advanced”:

  1. Click the Auto-Format Button to show or hide the Auto-Format Addon.
  2. Click tab “Advanced” to view the individual fixer units that perform auto formatting
  3. Click a category to see the individual files
  4. Right-click a fixing unit to open the underlying PowerShell code, or the description file, or the file location.
  5. Click “Apply” to run this fixer unit only.

Advanced Autofix Options

Creating Functions

ISESteroids can create function bodys for you in a number of convenient ways:

Syntax to Function

If you are familiar with command syntax description, then you can start to design a new function by defining its syntax. Simply enter the desired syntax into the editor, and do not worry about error messages from PowerShell (since you are not entering valid PowerShell code).

Let’s take for example a syntax like this:


Add-Customer [-Name] <string> [[-ID] <int>] [-Credential <PSCredential>] -Force

The syntax definition defines function name, parameter names (prefix “-“) and argument data types (enclosed in “<>”). It also marks optional parts in brackets. So this example reads:

  • The function name should be “Add-Customer”
  • The first parameter should be -Name of type “String” (Text), and the parameter should be mandatory and positional (you can omit the parameter, but not the argument)
  • The second parameter is completely optional, but you can also specify it positional (just the argument)
  • The third parameter is optional and not positional. If you use it, you must specify the parameter name and the argument.
  • The forth parameter is a Switch parameter. It has no value.

Select the syntax definition. In the left editor margin, a “Hammer” icon appears. Click it, and choose “Convert Syntax to Function” (this command is disabled when the selected syntax definition is invalid).

ISESteroids creates the function code that matches your syntax definition:

function Add-Customer
{
  [CmdletBinding()]
  param
  (
   [Parameter(Position=0, Mandatory=$true)]
   [string]
   $Name,

   [Parameter(Position=1, Mandatory=$false)]
   [int]
   $ID,

   [Parameter(Mandatory=$false)]
   [pscredential]
   [System.Management.Automation.Credential()]
   $Credential,

   [Parameter(Mandatory=$false)]
   [switch]
   $Force
  )


  # TODO: place your function code here
  # this code gets executed when the function is called
  # and all parameters have been processed


}

You can even define multiple parameter sets by selecting more than one syntax definition. Here is an example:


Connect-Server [-ID] <int> [[-ComputerName] <string>] 
Connect-Server [-ID] <int> [-ComputerName] <string> -Credential <PSCredential>

When you select both lines and convert them to function, the code looks like this:

function Connect-Server
{
  [CmdletBinding(DefaultParameterSetName='ParameterSet1')]
  param
  (
   [Parameter(ParameterSetName='ParameterSet1', Position=0, Mandatory=$true)]
   [Parameter(ParameterSetName='ParameterSet2', Position=0, Mandatory=$true)]
   [int]
   $ID,

   [Parameter(ParameterSetName='ParameterSet1', Position=1, Mandatory=$false)]
   [Parameter(ParameterSetName='ParameterSet2', Position=1, Mandatory=$true)]
   [string]
   $ComputerName,

   [Parameter(ParameterSetName='ParameterSet2', Mandatory=$true)]
   [pscredential]
   [System.Management.Automation.Credential()]
   $Credential
  )


  # TODO: place your function code here
  # this code gets executed when the function is called
  # and all parameters have been processed
  $chosenParameterSet = $PSCmdlet.ParameterSetName
  switch($chosenParameterSet)
  {
   'ParameterSet1'    { 'User has chosen ParameterSet1' } 
   'ParameterSet2'    { 'User has chosen ParameterSet2' } 
  }



}

And when you run the code, you will see that the newly created function “Connect-Server” behaves like intended: you must specify -id, you can specify -ComputerName, but if you do specify -Credential, then -ComputerName becomes mandatory:

PS C:\> Connect-⁠Server
cmdlet Connect-⁠Server at command pipeline position 1
Supply values for the following parameters:
ID: 12
User has chosen ParameterSet1

PS C:\> Connect-⁠Server -⁠id 12
User has chosen ParameterSet1

PS C:\> Connect-⁠Server -⁠id 12 -⁠ComputerName pc1
User has chosen ParameterSet1

PS C:\> Connect-⁠Server -⁠id 12 -⁠Credential testuser
cmdlet Connect-⁠Server at command pipeline position 1
Supply values for the following parameters:
ComputerName: pc2
User has chosen ParameterSet2

PS C:\> Connect-⁠Server -⁠id 12 -⁠ComputerName pc1 -⁠Credential testuser
User has chosen ParameterSet2

PS C:\>

Code to Function

Here is some code that you might have created after some playing around with PowerShell:


$ComputerName = 'microsoft.com'
[System.Net.DNS]::GetHostByName($ComputerName)

Now you would like to turn it into a function. Simply select the code, then right-click the selection, and in the context menu choose “Selection / Auto-Create Function”. A dialog opens:

Function from Code

The dialog lets you pick a function name. Carefully review the variables detected in the “Parameter” section. Check only those that you want to turn into parameters. Do not check variables that serve internal purposes and should not be exposed to the user. Then, click OK. The code is generated for you:

function Get-HostByName
{
  <#
  .SYNOPSIS
  Short Description
  .DESCRIPTION
  Detailed Description
  .EXAMPLE
  Get-HostByName
  explains how to use the command
  can be multiple lines
  .EXAMPLE
  Get-HostByName
  another example
  can have as many examples as you like
 #>

  [CmdletBinding()]
  param
  (
   [Parameter(Mandatory=$false, Position=0)]
   [System.String]
   $ComputerName = 'microsoft.com'
  )

  [System.Net.DNS]::GetHostByName($ComputerName)
}

Cmdlet to Function

If you’d like to find out how a particular cmdlet defined its parameters so you can mimick this in own functions, have ISESteroids create a function body from cmdlet. To do this, enter the cmdlet name that you want to mimick, for example “Get-Random”.

Then, right-click it and choose “Source Code / Convert to Function”. This is the resulting code:


# This function mimicks the interface of the cmdlet 'Get-Random'
# It supports the same parameters and parameter sets

# This function actually does nothing. There is no code inside the begin, process, or end block.
# It serves as an example for parameter block definitions only.
#
# Run this code, then type in the function name, and try the parameters:

# Get-MyRandom -   <- here you will get the parameter completion in ISE. You can also press TAB for code completion
#
# Note: you will not see dynamic parameters and associated parameter sets that the underlying Cmdlet may expose.
# Dynamic parameters are typically added by a provider.


<#
  .SYNOPSIS
    <A brief description of the script>
  .DESCRIPTION
    <A detailed description of the script>

  .PARAMETER SetSeed
     <Parameter Description>
  .PARAMETER Maximum
     <Parameter Description>
  .PARAMETER Minimum
     <Parameter Description>
  .PARAMETER InputObject
     <Parameter Description>
  .PARAMETER Count
     <Parameter Description>

  .EXAMPLE
    <An example of using the script>
  .EXAMPLE
     <An example of using the script>
#>



function Get-MyRandom
{
   [CmdletBinding(DefaultParameterSetName='RandomNumberParameterSet', HelpUri='http://go.microsoft.com/fwlink/?LinkID=113446', RemotingCapability='None')]
   param
   (

     [ValidateNotNull()]
     [System.Nullable[int]]
     $SetSeed,

     [Parameter(ParameterSetName='RandomNumberParameterSet', Position=0)]
     [System.Object]
     $Maximum,

     [Parameter(ParameterSetName='RandomNumberParameterSet')]
     [System.Object]
     $Minimum,

     [Parameter(ParameterSetName='RandomListItemParameterSet', Mandatory=$true, Position=0, ValueFromPipeline=$true)]
     [ValidateNotNullOrEmpty()]
     [System.Object[]]
     $InputObject,

     [Parameter(ParameterSetName='RandomListItemParameterSet')]
     [ValidateRange(1, 2147483647)]
     [int]
     $Count
   )


   begin 
   {
     # place initialization code here that gets executed once at the beginning
   }

   process
   {
     # place code here that gets executed for each incoming pipeline object'
   }

   end
   {
     # place clean-up code here that gets executed once at the end'
   }
}

You can immediately see how Get-Random defined its parameters, and learn from it. You may discover new Validate attributes and find out about parameter Aliases.

.NET Method to Function

Static .NET methods can be turned into true PowerShell functions with just a click. Here is a static .NET method that can set an environment string:


[Environment]::SetEnvironmentVariable("test", "12", "process")

A raw .NET call does not tell the user what arguments are required, has no error handling, is not easily reusable. To turn the .NET method SetEnvironmentVariable into a PowerShell function, right-click the method and choose “Refactor / Turn method into function”. Here is the resulting PowerShell code:

<#
 Converting method "SetEnvironmentVariable" of type [System.Environment] to PowerShell
 2 overloads found.
 created 2 PowerShell functions.
 PowerShell code auto-created by ISESteroids 2.0 Enterprise
#>


# Overload #1:
function Set-EnvironmentVariable
{
  [CmdletBinding()]
  param
  (
   [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
   [String]
   $variable,

   [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
   [String]
   $value

  )

  process
  {
   try
   {
    [System.Environment]::SetEnvironmentVariable($variable, $value)
   }
   catch
   {
    Write-Warning "Error occured: $_"
   }
  }
}

# Overload #2:
function Set-EnvironmentVariable
{
  [CmdletBinding()]
  param
  (
   [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
   [String]
   $variable,

   [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
   [String]
   $value,

   [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
   [EnvironmentVariableTarget]
   $target

  )

  process
  {
   try
   {
    [System.Environment]::SetEnvironmentVariable($variable, $value, $target)
   }
   catch
   {
    Write-Warning "Error occured: $_"
   }
  }
}


Note that ISESteroids may create a number of functions, one for each overload. Pick just the one you want to keep, and delete the rest. Once the .NET static method is turned into a PowerShell function, it is very easy to use and can be exported to modules. Here you can see how easy it is now to set environment variables with your new function:

New Function In Use

Snippet Composition

You can compose new functions with snippets. When you enter “func”, the keyword gets a cyan background, and when you now press TAB, a function body is inserted, and you can assign it a name, then press ENTER.

The caret is moved inside the function. Next, enter “par”. Again, the keyword gets a cyan background indicating that there is a snippet available. Press TAB to insert a param() block, choose a name for the first parameter, and press TAB. This moves focus to the second parameter. Assign it a name, too, and press ENTER.

You can create more snippets (see “Advanced Code Snippets”) to tailor function creation exactly to your needs.

Creating Error Handlers

When your script encounters an unhandled runtime exception, PowerShell typically emits one of its red error messages:

PS C:\> . 'C:\Users\tobwe\Documents\powershell\test.ps1
Stop-⁠Service : Cannot find any service with service name 'DoesNotExist'.
At line:1 char:1
+ Stop-⁠Service -⁠Name DoesNotExist -⁠ErrorAction Stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (DoesNotExist:String) [Stop-⁠Service], ServiceCommandException
    + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.StopServiceCommand


PS C:\>

Using Simple Error Messages

You can right-click the console and enable “Simple Error Messages” to reduce the clutter and only show the essential information, which reduces the exception message like this:

PS C:\> . 'C:\Users\tobwe\Documents\powershell\test.ps1
[1,1: Stop-⁠Service] Cannot find any service with service name 'DoesNotExist'.

PS C:\>

Shortening error messages works only for terminating errors. It does not change the error message emitted from cmdlet that use -ErrorAction Stop.

If the error was caused by some command in your script, ISESteroids marks it with a red squiggle. You can hover over it to find out more about the exception:

Runtime Error

Adding Error Hander Code

If you want to respond to the error, you need to add an error handler. ISESteroids takes out the complexity and adds an appropriate handler for you.

In the left editor margin, a red light-bulb indicates there are auto-fixes available. Click the bulb, and choose “Add specific error handler”. ISESteroids automatically inserts an error handler that is able to catch this exception. If necessary, it also adds the -ErrorAction parameter to the cmdlet call. All edits are marked with a green background that is removed as soon as you click or edit the script:

Error Handler

When you take a closer look at the inserted error handler, you’ll discover that ISESteroids automatically determined the exception type and added a specific catch clause for this exception type. Any other error will still show up. If other errors cause runtime exceptions, repeat the steps above to add more specific catch clauses.

try
{
     Stop-Service -Name DoesNotExist -ErrorAction Stop
}
# NOTE: When you use a SPECIFIC catch block, exceptions thrown by -ErrorAction Stop MAY LACK
# some InvocationInfo details such as ScriptLineNumber.
# REMEDY: If that affects you, remove the SPECIFIC exception type [Microsoft.PowerShell.Commands.ServiceCommandException] in the code below
# and use ONE generic catch block instead. Such a catch block then handles ALL error types, so you would need to
# add the logic to handle different error types differently by yourself.
catch [Microsoft.PowerShell.Commands.ServiceCommandException]
{
     # get error record
     [Management.Automation.ErrorRecord]$e = $_

     # retrieve information about runtime error
     $info = [PSCustomObject]@{
         Exception = $e.Exception.Message
         Reason    = $e.CategoryInfo.Reason
         Target    = $e.CategoryInfo.TargetName
         Script    = $e.InvocationInfo.ScriptName
         Line      = $e.InvocationInfo.ScriptLineNumber
         Column    = $e.InvocationInfo.OffsetInLine
     }

     # output information. Post-process collected info, and log info (optional)
     $info
}

Note also that ISESteroids stores the error record in $_ into a strongly-typed variable $e. This way, when using $e you get full IntelliSense.

Simplifying Error Handler

The inserted error handler is just an initial example. You can always take the information gathered in $info, and write it to some log file, or simplify and change the error handler as you like. Just tailor it to your needs:

try
{
     Stop-Service -Name DoesNotExist -ErrorAction Stop
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException]
{
     [Management.Automation.ErrorRecord]$e = $_
  Write-Warning $e.Exception.Message
}