PowerShellTraps

I have been collecting various PowerShell programming issues for long time.
They were not necessarily bugs but rather pitfalls and oddities that caused my
mistakes. Finally, I put them all together to the GitHub repository PowerShellTraps.

Leave a comment

Enable debugging on terminating errors in PowerShell

PowerShell provides several alternative actions on errors:

  • SilentlyContinue
  • Stop
  • Continue
  • Inquire
  • Ignore (V3)

They are set by the variable $ErrorActionPreference in the current scope as the default for all commands or by the common parameter ErrorAction for an individual command.

Unfortunately there is no such an action as Debug on terminating errors which is useful on troubleshooting. The closest that we can get out of the box is the error action Inquire. It is useful in many cases but it is not always convenient. For example, it may produce a lot of noise on legitimate non terminating errors. Also, although it allows entering the nested prompt (Suspend) for examining the current situation this is not the same as entering the debugging mode which allows stepping further, for example.

Fortunately there is a way to enable debugging on terminating errors. It exploits the fact that the system variable StackTrace is updated in such cases. Thus, setting the variable breakpoint for StackTrace writes actually turns debugging on terminating errors on.

Here is the simple script Debug-Error.ps1 which turns debugging on terminating errors on and off. Ideally, it should be located in the path, so that commands Debug-Error and Debug-Error -Off are always available.

Leave a comment

Helps module – PowerShell MAML help builder

In order to create help files for one of my PowerShell modules I did not find any better solution than to invent my own MAML help builder. Here it is:

https://github.com/nightroman/Helps

The module builds PowerShell MAML help files from PowerShell help scripts. Help scripts are almost WYSIWYG, they look very similar to the result help. Still, they are just scripts and this makes a lot of useful features easy. One of them is building help files for several cultures.

The module builds help for cmdlets, functions, scripts, and providers. Here is the template help source script:


<#
.SYNOPSIS
    Template help source script.

.DESCRIPTION
    The help source script returns hashtables describing command/provider help.
    The caller or the script itself should make all the commands available.

    Synopsis, description, remarks, etc. can be strings or string arrays.

    Each string is shown as a new line. Strings with leading tabs or spaces are
    treated as pre-formatted blocks. Other strings are formatted: sequences of
    not empty not indented lines are joined together with single spaces.
#>

### Command help data

<#
* Mandatory keys: 'command', 'synopsis', 'parameters'.
* 'description': the default value is the 'synopsis' text.
* 'sets' keys are parameter set names, values are remarks.
* 'parameters' keys are parameter names, values are remarks.
* 'examples.title': the default is generated: --- EXAMPLE N ---
* 'examples.code': [ScriptBlock] or [string].
    [ScriptBlock] is called by 'test' called by Test-Helps.
* 'examples.test': [ScriptBlock].
    $args[0] is the example code being tested.
#>
@{
    command = 'Name'
    synopsis = '...'
    description = '...'
    sets = @{
        Set1 = '...'
        #...
    }
    parameters = @{
        Param1 = '...'
        #...
    }
    inputs = @(
        @{
            type = '...'
            description = '...'
        }
        #...
    )
    outputs = @(
        @{
            type = '...'
            description = '...'
        }
        #...
    )
    notes = '...'
    examples = @(
        @{
            title = '...'
            introduction = '...'
            code = {
            }
            remarks = '...'
            test = {
                . $args[0]
            }
        }
        #...
    )
    links = @(
        @{
            text = '...'
            URI = '...'
        }
        #...
    )
}

### Provider help data

<#
Provider help items are similar to command help items with same names.
The are a few differences:
* Mandatory keys: 'provider', 'synopsis'.
* 'examples': 'introduction' and 'code' are not joined.
#>
@{
    provider = 'Name'
    drives = '...'
    synopsis = '...'
    description = '...'
    capabilities = '...'
    tasks = @(
        @{
            title = '...'
            description = '...'
            examples = @(
                @{
                    title = '...'
                    introduction = '...'
                    code = {
                    }
                    remarks = '...'
                    test = {
                        . $args[0]
                    }
                }
            )
        }
        #...
    )
    parameters = @(
        @{
            name = '...'
            type = '...'
            description = '...'
            cmdlets = '...'
            values = @(
                @{
                    value = '...'
                    description = '...'
                }
                #...
            )
        }
        #...
    )
    notes = '...'
    links = @(
        @{
            text = '...'
            URI = '...'
        }
        #...
    )
}

Leave a comment

How to run PowerShell scripts “from Explorer”

This is a popular question because after installation of PowerShell its scripts (.ps1 files) are not associated with PowerShell.exe, as one may expect. I believe there are many good security reasons for this approach. But in my case this is not suitable for many other good reasons. I want to invoke my scripts from Explorer by mouse double clicks, just like I invoke .bat, .cmd, .js, .vbs, .pl and other script files.

I tried quite a few ways of doing this and I am going to describe just one which is in active use for several years. The PowerShell script below should be invoked once to install the association (actually under the covers it calls Cmd to make the job done by FType):


Install-Association.ps1

<#
.SYNOPSIS
    Associates .ps1 files with PowerShell.exe
#>

cmd /c @"
FType Microsoft.PowerShellScript.1=$PSHOME\powershell.exe -NoExit . "'%1'" %*; Exit
"@

Description

Basically the command tells to start PowerShell.exe and invoke a script (%1) by dot-sourcing it (operator .) with optional arguments (%*). But the command line also contains two contradicting exit related instructions: switch -NoExit for PowerShell.exe and instruction Exit for PowerShell. Why?

  • If a script succeeds its console window is closed due to the Exit statement in the end. Of course, scripts with screen output to look at should not be invoked in this way.
  • If a script fails its console window is not closed because of -NoExit switch and not yet executed due to a failure Exit statement.

After a failure one can:

  • Read the output and error messages.
  • Investigate the variables, environment and etc. Note: a script is dot-sourced (invoked by .), that often makes more variables available in the global scope after failures.
  • Fix transient problems and re-run the script in its still opened console with a full set of original parameters simply by typing r immediately or by r 1 after any command invoked.

"'%1'" would be enough for invoking scripts from Explorer. But due to %* the association also works in other scenarios with extra arguments passed in a script. For example in Cmd or Run window it is enough to provide a script path and a few simple (!) arguments, PowerShell.exe can be omitted.


Limitations

  • This approach does not work if a script full path contains '. But I do not really suffer from this limitation because my scripts are never in such paths. As for other scripts, I do not invoke them from Explorer, for mentioned safety reasons, too.
  • %* (extra arguments) does not work (directly) with not trivial arguments with spaces and $ or ` symbols. For such scenarios there is yet another approach with -File parameter of PowerShell.exe. But an association command with -File does not provide described above flexibility of exit.

Disclaimer

If you use this then responsibility is all yours. Do not invoke unknown or not trivial scripts via association, results may be unwanted. But in trivial cases the described approach really helps to save seconds here and there.


1 Comment

Auto-loaded Functions

Calling a loaded function from memory is more effective than calling a script file from disk. But it may be not a good idea to preload all the functions that can be potentially used if many of them actually may not be used in a session. The described technique of "auto-loaded functions" solves the dilemma: you load only what you really use and it is loaded once only on the first call.

How to use

  • Put a script into a directory included into the system path;
  • Call it from your code just by name, with no path or extension (e.g. just Encode-Xml in our example).

How it works

On the first call of Encode-Xml (with no path or extension!) from any code the script Encode-Xml.ps1 is invoked. It takes its parameters, installs the global function Encode-Xml (exactly the same name!) and calls it with all the parameters passed into the script (@PSBoundParameters). That’s it, from now on Encode-Xml calls the installed function, not the source script (function precedence is higher).

Example script with an auto-loaded function


<#
.SYNOPSIS
    Encodes literal text into XML text or attribute value.

.NOTES
    Autoloaded function.
#>

param
(
    [string]
    $Text = $(throw)
)

function global:Encode-Xml
(
    [string]
    $Text = $(throw)
)
{
    if ($args) { throw "Invalid parameters: $args" }
    $Text.Replace('&', '&amp;').Replace("'", '&apos;').Replace('"', '&quot;').Replace('<', '&lt;').Replace('>', '&gt;')
}

Encode-Xml @PSBoundParameters


See also

Organizing Your PowerShell Scripts and Functions by Joel ‘Jaykul’ Bennett

Leave a comment

Get names of script parameters


This function parses parameters of a script and returns parameter names. For example, I use it in my own TabExpansion procedure. It requires PowerShell 2.0.


function GetScriptParameter
(
    # Full script path.
    $Path,
    # Script code (if $Path is not defined).
    $Script,
    # Parameter wildcard pattern (to get a subset).
    $Pattern
)
{
    if ($Path) {
        $Script = [System.IO.File]::ReadAllText($Path)
    }

    $mode = 0
    $param = $true
    $tokens = @([System.Management.Automation.PSParser]::Tokenize($Script, [ref]$null))
    for($i = 0; $i -lt $tokens.Count; ++$i) {
        $t = $tokens[$i]

        # skip [ whatever ]
        if (($t.Type -eq 'Operator') -and ($t.Content -eq '[')) {
            $level = 1
            for(++$i; $i -lt $tokens.Count; ++$i) {
                $t = $tokens[$i]
                if ($t.Type -eq 'Operator') {
                    if ($t.Content -eq '[') {
                        ++$level
                    }
                    elseif($t.Content -eq ']') {
                        --$level
                        if ($level -le 0) {
                            break
                        }
                    }
                }
            }
            continue
        }

        switch($t.Type) {
            'NewLine' { break }
            'Comment' { break }
            'Command' {
                if ($mode -le 1) {
                    return
                }
                break
            }
            'Keyword' {
                if ($mode -eq 0) {
                    if ($t.Content -eq 'param') {
                        $mode = 1
                        break
                    }
                }
            }
            'GroupStart' {
                if ($mode) {
                    ++$mode
                    break
                }
                else {
                    return
                }
            }
            'GroupEnd' {
                --$mode
                if ($mode -lt 2) {
                    return
                }
            }
            'Variable' {
                if ($mode -eq 2 -and $param) {
                    $param = $false
                    if ((!$Pattern) -or ($t.Content -like $Pattern)) {
                        $t.Content
                    }
                    break
                }
            }
            'Operator' {
                if (($mode -eq 2) -and ($t.Content -eq ',')) {
                    $param = $true
                }
            }
        }
    }
}

1 Comment

V2: Debugger for Graphical PowerShell V2 CTP2

V1 is here…

 

What’s new in V2
  • ProcessDebuggerStop is called dot-sourced, so that debug expressions are invoked in the current scope and normally you can change local variables directly;
  • Local variables of ProcessDebuggerStop moved to $global:__DebuggerStop members (to avoid clashes with real variables and debug expressions);
  • 10 ($__DebuggerStop.MaximumHistoryCount) last debug expressions are kept in history and shown in the dialog, you can copy and paste them;
  • <enter> repeats the last step or step-over command (similar to ConsoleHost).
  • Added two new debug commands (Keith Hill’s idea and the code):
    • <number>: show debug location in context of <number> lines;
    • + <number>: set location context preference to <number> lines ($__DebuggerStop.ShowDebugLocation). Default is 0, so that only position messages are shown; if it is positive then debug location is shown in context of <number> of lines.

Download: Utility_GPowerShellDebugger2.zip (EDIT: the next version is here…

Leave a comment

Debugger for Graphical PowerShell V2 CTP2

Graphical PowerShell V2 CTP2 still looks like a demo, basically speaking. Nevertheless, I find it already suitable for some tasks. Unfortunately it does not implement a few host features including default breakpoint debugger action. Breakpoints without action are simply ignored. Fortunately PowerShell provides a way to solve this. Here it is: simple and yet practically useful Add-Debugger.ps1. The archive also contains Test-Debugger.ps1 to play with and see how debugging goes (in any host, actually). Follow instructions in the script headers.

Download: Utility_GPowerShellDebugger.zip (EDIT: the next version is here…)

Leave a comment

PowerShell.hrc for Colorer, version 1.3.4


PowerShell.hrc for Colorer, version 1.3.4

 

EDIT: This version is obsolete. The latest PowerShell.hrc is included into the featured version of PowerShellFar plug-in for Far Manager: http://code.google.com/p/farnet/

 

What’s new: REGEX and SQL schemes are supported in PowerShell literal strings.

Flexibility and power of COLORER is amazing – it not only supports tons of file formats, but also allows combining schemes together (e.g. HTML scheme supports embedded JScript, VBScript and etc. reusing their schemes). As for the PowerShell, in new version of PowerShell.hrc I tried to use REGEX and SQL schemes in literal strings.

  • REGEX scheme is used in string operand of -match, -notmatch, -replace and etc.
  • Here strings are processed with REGEX or SQL schemes starting with lines #REGEX or --SQL respectively. Note that regex normally should be created with option IgnorePatternWhitespace or it should use inline instruction (?x).

Examples of highlighting in PowerShell strings

### Regex in operands

"ab11* 'foo'" -notmatch '^ab[0-2](d+)*s+''(w+)\'''
'ab11* "foo"' -match "^ab[0-2]$(2 - 1)*s+$($null)$null`"foo\`""
'2007-9-23' -replace 'b(d{2,4})-([01]?d)-([012]?d)b', '$3/$2/$1'

### Regex in here strings

$re = @'
#REGEX Id and Name
#(?<Id>d+)           # #number
s+                    # spaces
(?<Name>[_a-zA-Z]w*)  # name
'@

# how to use such a pattern:
New-Object Regex $re, 'IgnorePatternWhitespace'

### SQL in here strings

@'
--SQL single quoted
SELECT Data1, COUNT(1)
FROM Table1
WHERE Data1 = '$literal'
GROUP BY Data1
'@

@"
--SQL double quoted
SELECT Data1, COUNT(1)
FROM $Table
WHERE $x = '$variable'
GROUP BY Data1
"@


Leave a comment

Example of a PowerShell script converted to HTML with syntax highlighting


This is not a real script, it is a script used for testing of syntax description PowerShell.hrc for Colorer and covering related issues. Perhaps this HTML looks like a Christmas Tree, no problem, it is just a demo. One can configure Colorer according to his preferences. Take a look at how it handles and distinguishes expandable and not expandable string content and escaping rules, is not it a beauty?


Demo: highlighted PowerShell

EDIT: replaced large demo text with a link

1 Comment